terminal_rb 0.12.1 → 0.12.2

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: 834587f3bf9e66077d0ad33b8391100863ec8932e4eafd31077684c8a982d03c
4
- data.tar.gz: fd95fac927978b7bdbc39d71797d165b42a787a6a35139418c6a445192258e83
3
+ metadata.gz: 76a55505ad930b5c59af396e566e1079e3aa0c339d4ef68d5adf559c243c549a
4
+ data.tar.gz: 87882ec5820678e2f657d739588cd8502167aa68c5fffad47296903b387ced94
5
5
  SHA512:
6
- metadata.gz: 7f5284ff90de5ee142d3e11414df40148eef0dca7bb216e000f4f7a3474c416bba644cdb2a44656612eae33bb7e0933424ef34161a7cd247b89054ab99538bbe
7
- data.tar.gz: 979db236870e067f58d25e18ca4bc5ce088c1558b8db8c59e95017656c28cf6e762593849fa4e9d44a2d716654b81e02d643ca43d2ba83528410070c8a8e70c7
6
+ metadata.gz: e9fffe327952f951928e43e2b29aec523f87794c499b1b9c81a483a529653c4434f3422e0a32b0ed2c08a3886a50a3f74f10704a50c5397867c470cc97a974ac
7
+ data.tar.gz: fee9b96d3b899667b772fb68590ecea6476c904279b374132d30fca959f5c1cc4559709a600302cb71aeb78eb4a9b128cc7167a64efd835ddc96e93f10c5b85a
data/README.md CHANGED
@@ -14,7 +14,7 @@ Terminal access with super fast ANSI control codes support and [BBCode-like](htt
14
14
  - [NO_COLOR convention](https://no-color.org) support
15
15
  - markup of text attributes and colors using a [BBCode](https://en.wikipedia.org/wiki/BBCode)-like syntax
16
16
  - calculation for correct display width of strings containing Unicdode chars inclusive emojis
17
- - generation of word-by-word wrapped text
17
+ - word-wise line break generator
18
18
  - supports [CSIu protocol](https://sw.kovidgoyal.net/kitty/keyboard-protocol)
19
19
 
20
20
  ## Examples
@@ -1,19 +1,39 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Terminal
4
- module AsKeyEvent
4
+ #
5
+ # Key event reported from {read_key_event}.
6
+ #
7
+ class KeyEvent
5
8
  class << self
9
+ # @attribute [w] caching
10
+ # @return [true, false] whether KeyCodes should be cached
11
+ def caching = !!@cache
12
+
13
+ # @attribute [w] caching
14
+ def caching=(value)
15
+ if value
16
+ @cache ||= {}
17
+ else
18
+ @cache = nil
19
+ end
20
+ end
21
+
22
+ # Translate a keyboard input string into a related KeyEvent
23
+ #
24
+ # @param [String] raw keyboard input
25
+ # @return [KeyEvent] related key event
6
26
  def [](raw)
7
- return KeyEvent.new(raw, *@single_key[raw.ord]) if raw.size == 1
8
- return KeyEvent.unknown(raw) if raw[0] != "\e"
27
+ return new(raw, *@single_key[raw.ord]) if raw.size == 1
28
+ return unknown(raw) if raw[0] != "\e"
9
29
  return esc1(raw, raw[1]) if raw.size == 2 # ESC ?
10
30
  case raw[1]
11
31
  when "\e" # ESC ESC ...
12
32
  return esc_esc(raw)
13
33
  when 'O'
14
- return KeyEvent.new(raw, *@ss3[raw[2].ord]) if raw.size == 3 # ESC O ?
34
+ return new(raw, *@ss3[raw[2].ord]) if raw.size == 3 # ESC O ?
15
35
  when '['
16
- return KeyEvent.new(raw, *@ss3[raw[2].ord]) if raw.size == 3 # ESC [ ?
36
+ return new(raw, *@ss3[raw[2].ord]) if raw.size == 3 # ESC [ ?
17
37
  if raw.size == 6 && raw[2] == 'M' # ESC [ M b c r
18
38
  return mouse_vt200(raw)
19
39
  end
@@ -29,18 +49,25 @@ module Terminal
29
49
  return mouse_sgr(raw) if raw[2] == '<'
30
50
  end
31
51
  end
32
- KeyEvent.unknown(raw)
52
+ unknown(raw)
53
+ end
54
+
55
+ # @!visibility private
56
+ def new(raw, key = raw, modifier = 0, extra = nil)
57
+ @cache ? (@cache[raw] ||= super.freeze) : super.freeze
33
58
  end
34
59
 
35
60
  private
36
61
 
62
+ def unknown(raw) = new(raw, nil)
63
+
37
64
  def csi_u(raw)
38
65
  # ESC [ <code> u
39
66
  # ESC [ <code> ; <modifier> u
40
- return KeyEvent.unknown(raw) if raw.size < 4
67
+ return unknown(raw) if raw.size < 4
41
68
  idx = raw.index(';')
42
69
  code = raw[2..(idx ? idx - 1 : -2)].to_i
43
- KeyEvent.new(
70
+ new(
44
71
  raw,
45
72
  @csiu[code] || code.chr(Encoding::UTF_8),
46
73
  idx ? [raw[(idx + 1)..-2].to_i - 1, 0].max : 0
@@ -50,9 +77,9 @@ module Terminal
50
77
  def legacy(raw)
51
78
  # ESC [ <code> ~
52
79
  # ESC [ <code> ; <modifier> ~
53
- return KeyEvent.unknown(raw) if raw.size < 4
80
+ return unknown(raw) if raw.size < 4
54
81
  idx = raw.index(';')
55
- KeyEvent.new(
82
+ new(
56
83
  raw,
57
84
  @csi[raw[2..(idx ? idx - 1 : -2)].to_i],
58
85
  idx ? [raw[(idx + 1)..-2].to_i - 1, 0].max : 0
@@ -62,23 +89,23 @@ module Terminal
62
89
  def csi1(raw)
63
90
  # ESC [ 1 ; [~ABCDEFHPQRS]
64
91
  # ESC [ 1 ; <modifier> [~ABCDEFHPQRS]
65
- return KeyEvent.unknown(raw) if raw.size < 5
92
+ return unknown(raw) if raw.size < 5
66
93
  key, modifier = @ss3[raw[-1].ord]
67
94
  modifier ||= 0
68
- return KeyEvent.new(raw, nil, modifier) unless key
95
+ return new(raw, nil, modifier) unless key
69
96
  modifier |= [raw[4..-2].to_i - 1, 0].max if raw.size > 5
70
- KeyEvent.new(raw, key, modifier)
97
+ new(raw, key, modifier)
71
98
  end
72
99
 
73
100
  def esc_esc(raw)
74
101
  return esc1(raw, raw[2]) if raw.size == 3 # ESC ESC ?
75
102
  ret = self[raw[1..]]
76
- KeyEvent.new(raw, ret.key, ret.modifier | 2) # ESC ESC ...
103
+ new(raw, ret.key, ret.modifier | 2) # ESC ESC ...
77
104
  end
78
105
 
79
106
  def esc1(raw, char)
80
107
  key, modifier = @esc1[char.ord]
81
- KeyEvent.new(raw, key || char, modifier || 2)
108
+ new(raw, key || char, modifier || 2)
82
109
  end
83
110
 
84
111
  def mouse_vt200(raw)
@@ -89,25 +116,25 @@ module Terminal
89
116
  def mouse_sgr(raw)
90
117
  # ESC [ < <code> ; <col> ; <row> M
91
118
  # ESC [ < <code> ; <col> ; <row> m
92
- return KeyEvent.unknown(raw) if raw.size < 8
119
+ return unknown(raw) if raw.size < 8
93
120
  bcr = raw[3..-2].split(';', 3).map!(&:to_i)
94
- bcr.size == 3 ? mouse_event(raw, *bcr) : KeyEvent.unknown(raw)
121
+ bcr.size == 3 ? mouse_event(raw, *bcr) : unknown(raw)
95
122
  end
96
123
 
97
124
  def mouse_urxvt(raw)
98
125
  # ESC [ <code> ; <col> ; <row> M
99
- return KeyEvent.unknown(raw) if raw.size < 8
126
+ return unknown(raw) if raw.size < 8
100
127
  bcr = raw[2..-2].split(';', 3).map!(&:to_i)
101
- bcr.size == 3 ? mouse_event(raw, *bcr) : KeyEvent.unknown(raw)
128
+ bcr.size == 3 ? mouse_event(raw, *bcr) : unknown(raw)
102
129
  end
103
130
 
104
131
  def mouse_event(raw, btn, col, row)
105
- return KeyEvent.unknown(raw) if btn < 0 || col < 1 || row < 1
132
+ return unknown(raw) if btn < 0 || col < 1 || row < 1
106
133
  key, btn = kind_of_mouse_event(btn)
107
134
  modifier = btn.allbits?(4) ? 1 : 0
108
135
  modifier += 2 if btn.allbits?(8)
109
136
  modifier += 4 if btn.allbits?(16)
110
- KeyEvent.new(raw, key, modifier, [col, row])
137
+ new(raw, key, modifier, [col, row])
111
138
  end
112
139
 
113
140
  def mouse_event_key(btn)
@@ -118,6 +145,79 @@ module Terminal
118
145
  end
119
146
  end
120
147
 
148
+ # Event string received from standard input.
149
+ # This can be a simple value like `"a"`or more complex input like
150
+ # `"\e[24;6~"` (for Shift+Ctrl+F12).
151
+ #
152
+ # @return [String] received event string
153
+ attr_reader :raw
154
+
155
+ # Pressed key without any modifiers.
156
+ # This can be a string for simple keys like `"a"` or a Symbol like `:F12`.
157
+ # @return [String, Symbol] key without modifiers
158
+ attr_reader :key
159
+
160
+ # Modifier key code. This represents the encoded key modifier like `Shift`
161
+ # or `Alt`.
162
+ #
163
+ # @return [Integer] modifier key code
164
+ attr_reader :modifier
165
+
166
+ # @comment for mouse events
167
+ # @!visibility private
168
+ attr_reader :extra
169
+
170
+ # Name of the key event.
171
+ # This can be a simple name like `"a"` or `"Shift+Ctrl+F12"` for combined
172
+ # keys.
173
+ #
174
+ # @return [String] key name
175
+ attr_reader :name
176
+
177
+ # @attribute [r] modifier?
178
+ # @return [true, false] whether a key modifier was pressed
179
+ def modifier? = @modifier != 0
180
+
181
+ # @attribute [r] simple?
182
+ # @return [true, false] whether a simple key was pressed
183
+ def simple? = @raw == @name
184
+
185
+ # All pressed keys.
186
+ # This is composed by all {modifier} and the {key}.
187
+ #
188
+ # @return [Array<Symbol, String>] all pressed keys
189
+ def to_a = @ary.dup
190
+
191
+ # @!visibility private
192
+ def to_ary = simple? ? [@raw] : [@raw, @name]
193
+
194
+ # @!visibility private
195
+ def to_s = @name.dup
196
+
197
+ # @!visibility private
198
+ def inspect = "<#{self.class.name} #{to_ary.map(&:inspect).join(' ')}>"
199
+
200
+ # @!visibility private
201
+ def freeze
202
+ @raw.freeze
203
+ @key.freeze
204
+ @extra.freeze
205
+ @name.freeze
206
+ super
207
+ end
208
+
209
+ private
210
+
211
+ def initialize(raw, key, modifier, extra)
212
+ @raw = raw
213
+ @key = key
214
+ @modifier = modifier
215
+ @extra = extra
216
+ @ary = MODIFIERS.filter_map { |b, n| n if modifier.allbits?(b) }
217
+ @ary << key if key
218
+ @name = @ary.join('+') # .encode(Encoding::UTF_8)
219
+ end
220
+
121
221
  @mouse_kind = %i[
122
222
  MButton1
123
223
  MButton2
@@ -310,6 +410,19 @@ module Terminal
310
410
  0x20 => :Space,
311
411
  0x7f => :Back
312
412
  }.compare_by_identity.freeze
413
+
414
+ @cache = {}
415
+
416
+ MODIFIERS = {
417
+ 1 => :Shift,
418
+ 2 => :Alt,
419
+ 4 => :Ctrl,
420
+ 8 => :Super,
421
+ 16 => :Hyper,
422
+ 32 => :Meta,
423
+ 64 => :Caps,
424
+ 128 => :Num
425
+ }.freeze
426
+ private_constant :MODIFIERS
313
427
  end
314
- private_constant :AsKeyEvent
315
428
  end
@@ -26,13 +26,13 @@ module Terminal
26
26
  # The input will be returned as named key codes like "Ctrl+c" by default.
27
27
  # This can be changed by `mode`.
28
28
  #
29
- # @deprecated Use arther {read_key_event} for better input handling.
29
+ # @deprecated Use rather {read_key_event} for better input handling.
30
30
  #
31
31
  # @param [:named, :raw, :both] mode modifies the result
32
32
  # @return [String] key code ("as is") in `:raw` mode
33
33
  # @return [String] key name in `:named` mode
34
34
  # @return [[String, String]] key code and key name in `:both` mode
35
- # @return [nil] in any error case
35
+ # @return [nil] in error case
36
36
  def read_key(mode: :named)
37
37
  event = read_key_event or return
38
38
  return event.raw if mode == :raw
@@ -43,7 +43,7 @@ module Terminal
43
43
  # Read next {KeyEvent} from standard input.
44
44
  #
45
45
  # @return [KeyEvent] next event
46
- # @return [nil] on any error
46
+ # @return [nil] in error case
47
47
  def read_key_event
48
48
  case input_mode
49
49
  when :dumb
@@ -52,23 +52,23 @@ module Terminal
52
52
  KeyEvent.new(raw, *opts)
53
53
  when :csi_u, :legacy
54
54
  # raw = with_mouse ? read_tty_with_mouse : read_tty
55
- AsKeyEvent[raw] if (raw = read_tty)
55
+ KeyEvent[raw] if (raw = read_tty)
56
56
  end
57
57
  end
58
58
 
59
59
  private
60
60
 
61
61
  def find_input_mode
62
+ # order is important!
62
63
  im = ENV['INPUT_MODE']
63
64
  return :dumb if im == 'dumb'
64
65
  return :legacy if im == 'legacy'
65
66
  return :dumb unless STDIN.tty?
66
67
  if ansi? && _write("\e[>1u\e[?u\e[c") && csi_u?
67
68
  at_exit { _write("\e[<u") }
68
- :csi_u
69
- else
70
- :legacy
69
+ return :csi_u
71
70
  end
71
+ :legacy
72
72
  rescue Interrupt
73
73
  :legacy
74
74
  rescue IOError, SystemCallError
@@ -130,120 +130,5 @@ module Terminal
130
130
  private_constant :DUMB_KEYS
131
131
  end
132
132
 
133
- #
134
- # Key event reported from {read_key_event}.
135
- #
136
- class KeyEvent
137
- class << self
138
- # @attribute [w] caching
139
- # @return [true, false] whether KeyCodes should be cached
140
- def caching = !!@cache
141
-
142
- # @attribute [w] caching
143
- def caching=(value)
144
- if value
145
- @cache ||= {}
146
- else
147
- @cache = nil
148
- end
149
- end
150
-
151
- # @!visibility private
152
- def new(raw, key = raw, modifier = 0, extra = nil)
153
- @cache ? (@cache[raw] ||= super.freeze) : super.freeze
154
- end
155
-
156
- # @!visibility private
157
- def unknown(raw) = new(raw, nil)
158
- end
159
-
160
- # Event string received from standard input.
161
- # This can be a simple value like `"a"`or `"\e[24;6~"` (for Shift+Ctrl+F12).
162
- #
163
- # @return [String] received event string
164
- attr_reader :raw
165
-
166
- # Pressed key without any modifiers.
167
- # This can be a string for simple keys like `"a"` or a Symbol like `:F12`.
168
- # @return [String, Symbol] key without modifiers
169
- attr_reader :key
170
-
171
- # Modifier key code. This represents the encoded key modifier like `Shift`
172
- # or `Alt`.
173
- #
174
- # @return [Integer] modifier key code
175
- attr_reader :modifier
176
-
177
- # @comment for mouse events
178
- # @!visibility private
179
- attr_reader :extra
180
-
181
- # Name of the key event.
182
- # This can be a simple name like `"a"` or `"Shift+Ctrl+F12"` for combined
183
- # keys.
184
- #
185
- # @return [String] key name
186
- attr_reader :name
187
-
188
- # @attribute [r] modifier?
189
- # @return [true, false] whether a key modifier was pressed
190
- def modifier? = @modifier != 0
191
-
192
- # @attribute [r] simple?
193
- # @return [true, false] whether a simple char was pressed
194
- def simple? = @raw == @name
195
-
196
- # All pressed keys.
197
- # This is composed by all {modifier} and the {key}.
198
- #
199
- # @return [Array<Symbol, String>] all pressed keys
200
- def to_a = @ary.dup
201
-
202
- # @!visibility private
203
- def to_ary = simple? ? [@raw] : [@raw, @name]
204
-
205
- # @!visibility private
206
- def to_s = @name.dup
207
-
208
- # @!visibility private
209
- def inspect = "<#{self.class.name} #{to_ary.map(&:inspect).join(' ')}>"
210
-
211
- # @!visibility private
212
- def freeze
213
- @raw.freeze
214
- @key.freeze
215
- @extra.freeze
216
- @name.freeze
217
- super
218
- end
219
-
220
- private
221
-
222
- def initialize(raw, key, modifier, extra)
223
- @raw = raw
224
- @key = key
225
- @modifier = modifier
226
- @extra = extra
227
- @ary = MODIFIERS.filter_map { |b, n| n if modifier.allbits?(b) }
228
- @ary << key if key
229
- @name = @ary.join('+').encode(Encoding::UTF_8)
230
- end
231
-
232
- @cache = {}
233
-
234
- MODIFIERS = {
235
- 1 => :Shift,
236
- 2 => :Alt,
237
- 4 => :Ctrl,
238
- 8 => :Super,
239
- 16 => :Hyper,
240
- 32 => :Meta,
241
- 64 => :Caps,
242
- 128 => :Num
243
- }.freeze
244
- private_constant :MODIFIERS
245
- end
246
-
247
- autoload :AsKeyEvent, "#{__dir__}/input/as_key_event.rb"
248
- private_constant :AsKeyEvent
133
+ autoload :KeyEvent, "#{__dir__}/input/key_event.rb"
249
134
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  RSpec.shared_context 'with Terminal.rb' do |ansi: true, application: :kitty, colors: 256, size: [25, 80], pos: [1, 1]|
4
4
  let(:stdout) { [] }
5
+ let(:stdin) { [] }
5
6
  let(:stdoutstr) { stdout.join }
6
7
 
7
8
  before do
@@ -9,21 +10,22 @@ RSpec.shared_context 'with Terminal.rb' do |ansi: true, application: :kitty, col
9
10
  allow(Terminal).to receive(:application).with(no_args).and_return(
10
11
  application
11
12
  )
13
+ allow(Terminal).to receive(:input_mode).with(no_args).and_return(
14
+ ansi ? :csi_u : :dumb
15
+ )
12
16
  allow(Terminal).to receive(:colors).with(no_args).and_return(
13
17
  colors == :true_color ? 16_777_216 : colors
14
18
  )
15
19
  allow(Terminal).to receive(:size).with(no_args).and_return(size)
16
20
  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
- if ansi
21
- bbc = ->(s) { Terminal::Ansi.bbcode(s) }
22
- nobbc = lambda(&:to_s)
23
- else
24
- bbc = ->(s) { Terminal::Ansi.plain(s) }
25
- nobbc = ->(s) { Terminal::Ansi.undecorate(s) }
26
- end
21
+
22
+ bbcode_ = {
23
+ true =>
24
+ ansi ?
25
+ ->(s) { Terminal::Ansi.bbcode(s) } :
26
+ ->(s) { Terminal::Ansi.plain(s) },
27
+ false => ansi ? lambda(&:to_s) : ->(s) { Terminal::Ansi.undecorate(s) }
28
+ }.compare_by_identity
27
29
 
28
30
  allow(Terminal).to receive(:<<) do |object|
29
31
  stdout.push(bbcode[object]) unless object.nil?
@@ -31,7 +33,7 @@ RSpec.shared_context 'with Terminal.rb' do |ansi: true, application: :kitty, col
31
33
  end
32
34
 
33
35
  allow(Terminal).to receive(:print) do |*objects, bbcode: true|
34
- bbcode = bbcode ? bbc : nobbc
36
+ bbcode = bbcode_[bbcode]
35
37
  objects.flatten.each { stdout.push(bbcode[_1]) unless _1.nil? }
36
38
  nil
37
39
  end
@@ -42,7 +44,7 @@ RSpec.shared_context 'with Terminal.rb' do |ansi: true, application: :kitty, col
42
44
  stdout.push("\n")
43
45
  next
44
46
  end
45
- bbcode = bbcode ? bbc : nobbc
47
+ bbcode = bbcode_[bbcode]
46
48
  objects.each do |s|
47
49
  next stdout.push("\n") if s.nil?
48
50
  stdout.push(s = bbcode[s])
@@ -55,5 +57,9 @@ RSpec.shared_context 'with Terminal.rb' do |ansi: true, application: :kitty, col
55
57
  stdout.push(object = object.to_s)
56
58
  object.bytesize
57
59
  end
60
+
61
+ allow(Terminal).to receive(:read_key_event) do
62
+ Terminal::KeyEvent[stdin.shift || raise('stdin buffer is empty')]
63
+ end
58
64
  end
59
65
  end
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Terminal
4
4
  # The version number of the gem.
5
- VERSION = '0.12.1'
5
+ VERSION = '0.12.2'
6
6
  end
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.12.1
4
+ version: 0.12.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Blumtritt
@@ -9,9 +9,8 @@ bindir: bin
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies: []
12
- description: |
13
- Terminal access with super fast ANSI control codes support and
14
- BBCode-like embedded text attribute syntax.
12
+ description: 'Terminal access with super fast ANSI control codes support, modern CSIu
13
+ input, word-wise line break, BBCode-like embedded text attribute syntax. '
15
14
  executables:
16
15
  - bbcode
17
16
  extensions: []
@@ -34,7 +33,7 @@ files:
34
33
  - lib/terminal/ansi/named_colors.rb
35
34
  - lib/terminal/detect.rb
36
35
  - lib/terminal/input.rb
37
- - lib/terminal/input/as_key_event.rb
36
+ - lib/terminal/input/key_event.rb
38
37
  - lib/terminal/rspec/helper.rb
39
38
  - lib/terminal/text.rb
40
39
  - lib/terminal/text/char_width.rb
@@ -65,5 +64,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
65
64
  requirements: []
66
65
  rubygems_version: 3.7.1
67
66
  specification_version: 4
68
- summary: Fast terminal access with ANSI and BBCode support.
67
+ summary: Fast terminal access with ANSI, CSIu, BBCode, word-wise line break support.
69
68
  test_files: []