terminal_rb 0.12.0 → 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: 357e535e9317609160d54c93a2a106a2a39d86a4c6740e93bbbccc01a2fcd42f
4
- data.tar.gz: 746ddada0e50edf61c7220d144d75de46530056138e8a9d1bc1793098fe8a824
3
+ metadata.gz: 76a55505ad930b5c59af396e566e1079e3aa0c339d4ef68d5adf559c243c549a
4
+ data.tar.gz: 87882ec5820678e2f657d739588cd8502167aa68c5fffad47296903b387ced94
5
5
  SHA512:
6
- metadata.gz: 90b2db6aa0cd6d3351ae8b7ec0f8df30a37e7bdad87050def22c47cbf48c94246efe927b15a612adbc713ff8b34971adc973721172da4702a1c7a433446db5e9
7
- data.tar.gz: f35bafc5ecc52b59360ecec503e32ed173486fa81b42eec99912ef545d64be888ff01f9d584c3a6f34dd45505d449ece87ae033e61f0bb0e9637d481ddb7f310
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,84 +1,73 @@
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
- if raw.size == 3
15
- # ESC O ?
16
- return KeyEvent.new(raw, *@ss3[raw[2].ord])
17
- end
18
- when '[' # ESC [ ...
19
- if raw.size == 3
20
- # ESC [ ?
21
- return KeyEvent.new(raw, *@ss3[raw[2].ord])
22
- end
23
- if raw.size == 6 && raw[2] == 'M'
24
- # ESC [ M b c r
34
+ return new(raw, *@ss3[raw[2].ord]) if raw.size == 3 # ESC O ?
35
+ when '['
36
+ return new(raw, *@ss3[raw[2].ord]) if raw.size == 3 # ESC [ ?
37
+ if raw.size == 6 && raw[2] == 'M' # ESC [ M b c r
25
38
  return mouse_vt200(raw)
26
39
  end
27
40
  return csi1(raw) if raw.start_with?("\e[1;") # ESC [ 1 ; ...
28
41
  case raw[-1]
29
- when '~'
42
+ when '~' # ESC [ ... ~
30
43
  return legacy(raw)
31
- when 'u'
44
+ when 'u' # ESC [ ... u
32
45
  return csi_u(raw)
33
- when 'M'
46
+ when 'M' # ESC [ ... M
34
47
  return raw[2] == '<' ? mouse_sgr(raw) : mouse_urxvt(raw)
35
- when 'm'
48
+ when 'm' # ESC [ ... m
36
49
  return mouse_sgr(raw) if raw[2] == '<'
37
50
  end
38
51
  end
39
- KeyEvent.unknown(raw)
40
- end
41
-
42
- private
43
-
44
- def mouse_vt200(raw)
45
- # ESC [ M b c r
46
- mouse_event(raw, *raw[3..].chars.map! { _1.ord - 32 })
52
+ unknown(raw)
47
53
  end
48
54
 
49
- def mouse_sgr(raw)
50
- # ESC [ < <code> ; <col> ; <row> M
51
- # ESC [ < <code> ; <col> ; <row> m
52
- return KeyEvent.unknown(raw) if raw.size < 8
53
- bcr = raw[3..-2].split(';', 3).map!(&:to_i)
54
- bcr.size == 3 ? mouse_event(raw, *bcr) : KeyEvent.unknown(raw)
55
+ # @!visibility private
56
+ def new(raw, key = raw, modifier = 0, extra = nil)
57
+ @cache ? (@cache[raw] ||= super.freeze) : super.freeze
55
58
  end
56
59
 
57
- def mouse_urxvt(raw)
58
- # ESC [ <code> ; <col> ; <row> M
59
- return KeyEvent.unknown(raw) if raw.size < 8
60
- bcr = raw[2..-2].split(';', 3).map!(&:to_i)
61
- bcr.size == 3 ? mouse_event(raw, *bcr) : KeyEvent.unknown(raw)
62
- end
60
+ private
63
61
 
64
- def mouse_event(raw, btn, col, row)
65
- return KeyEvent.unknown(raw) if btn < 0 || col < 1 || row < 1
66
- kind = btn & 3
67
- ka = btn >= 64 ? @mouse_wheel_kind : @mouse_kind
68
- btn -= kind
69
- modifier = btn.allbits?(4) ? 1 : 0
70
- modifier += 2 if btn.allbits?(8)
71
- modifier += 4 if btn.allbits?(16)
72
- KeyEvent.new(raw, ka[kind], modifier, [col, row])
73
- end
62
+ def unknown(raw) = new(raw, nil)
74
63
 
75
64
  def csi_u(raw)
76
65
  # ESC [ <code> u
77
66
  # ESC [ <code> ; <modifier> u
78
- return KeyEvent.unknown(raw) if raw.size < 4
67
+ return unknown(raw) if raw.size < 4
79
68
  idx = raw.index(';')
80
69
  code = raw[2..(idx ? idx - 1 : -2)].to_i
81
- KeyEvent.new(
70
+ new(
82
71
  raw,
83
72
  @csiu[code] || code.chr(Encoding::UTF_8),
84
73
  idx ? [raw[(idx + 1)..-2].to_i - 1, 0].max : 0
@@ -88,9 +77,9 @@ module Terminal
88
77
  def legacy(raw)
89
78
  # ESC [ <code> ~
90
79
  # ESC [ <code> ; <modifier> ~
91
- return KeyEvent.unknown(raw) if raw.size < 4
80
+ return unknown(raw) if raw.size < 4
92
81
  idx = raw.index(';')
93
- KeyEvent.new(
82
+ new(
94
83
  raw,
95
84
  @csi[raw[2..(idx ? idx - 1 : -2)].to_i],
96
85
  idx ? [raw[(idx + 1)..-2].to_i - 1, 0].max : 0
@@ -100,26 +89,135 @@ module Terminal
100
89
  def csi1(raw)
101
90
  # ESC [ 1 ; [~ABCDEFHPQRS]
102
91
  # ESC [ 1 ; <modifier> [~ABCDEFHPQRS]
103
- return KeyEvent.unknown(raw) if raw.size < 5
92
+ return unknown(raw) if raw.size < 5
104
93
  key, modifier = @ss3[raw[-1].ord]
105
94
  modifier ||= 0
106
- return KeyEvent.new(raw, nil, modifier) unless key
95
+ return new(raw, nil, modifier) unless key
107
96
  modifier |= [raw[4..-2].to_i - 1, 0].max if raw.size > 5
108
- KeyEvent.new(raw, key, modifier)
97
+ new(raw, key, modifier)
109
98
  end
110
99
 
111
100
  def esc_esc(raw)
112
101
  return esc1(raw, raw[2]) if raw.size == 3 # ESC ESC ?
113
102
  ret = self[raw[1..]]
114
- KeyEvent.new(raw, ret.key, ret.modifier | 2) # ESC ESC ...
103
+ new(raw, ret.key, ret.modifier | 2) # ESC ESC ...
115
104
  end
116
105
 
117
106
  def esc1(raw, char)
118
107
  key, modifier = @esc1[char.ord]
119
- KeyEvent.new(raw, key || char, modifier || 2)
108
+ new(raw, key || char, modifier || 2)
109
+ end
110
+
111
+ def mouse_vt200(raw)
112
+ # ESC [ M b c r
113
+ mouse_event(raw, *raw[3..].chars.map! { _1.ord - 32 })
114
+ end
115
+
116
+ def mouse_sgr(raw)
117
+ # ESC [ < <code> ; <col> ; <row> M
118
+ # ESC [ < <code> ; <col> ; <row> m
119
+ return unknown(raw) if raw.size < 8
120
+ bcr = raw[3..-2].split(';', 3).map!(&:to_i)
121
+ bcr.size == 3 ? mouse_event(raw, *bcr) : unknown(raw)
122
+ end
123
+
124
+ def mouse_urxvt(raw)
125
+ # ESC [ <code> ; <col> ; <row> M
126
+ return unknown(raw) if raw.size < 8
127
+ bcr = raw[2..-2].split(';', 3).map!(&:to_i)
128
+ bcr.size == 3 ? mouse_event(raw, *bcr) : unknown(raw)
129
+ end
130
+
131
+ def mouse_event(raw, btn, col, row)
132
+ return unknown(raw) if btn < 0 || col < 1 || row < 1
133
+ key, btn = kind_of_mouse_event(btn)
134
+ modifier = btn.allbits?(4) ? 1 : 0
135
+ modifier += 2 if btn.allbits?(8)
136
+ modifier += 4 if btn.allbits?(16)
137
+ new(raw, key, modifier, [col, row])
138
+ end
139
+
140
+ def mouse_event_key(btn)
141
+ kind = btn & 3
142
+ key = (btn >= 64 ? @mouse_wheel_kind : @mouse_kind)[kind]
143
+ # TODO: what about btn >= 128 for more mouse wheel events?
144
+ [btn - kind, key]
120
145
  end
121
146
  end
122
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
+
123
221
  @mouse_kind = %i[
124
222
  MButton1
125
223
  MButton2
@@ -312,6 +410,19 @@ module Terminal
312
410
  0x20 => :Space,
313
411
  0x7f => :Back
314
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
315
427
  end
316
- private_constant :AsKeyEvent
317
428
  end
@@ -9,7 +9,8 @@ module Terminal
9
9
  # @attribute [r] input_mode
10
10
  #
11
11
  # @return [:csi_u]
12
- # when CSIu protocol support
12
+ # when [CSIu protocol](https://sw.kovidgoyal.net/kitty/keyboard-protocol)
13
+ # supported
13
14
  # @return [:legacy]
14
15
  # for standard terminal
15
16
  # @return [:dumb]
@@ -25,13 +26,13 @@ module Terminal
25
26
  # The input will be returned as named key codes like "Ctrl+c" by default.
26
27
  # This can be changed by `mode`.
27
28
  #
28
- # @deprecated Use arther {read_key_event} for better input handling.
29
+ # @deprecated Use rather {read_key_event} for better input handling.
29
30
  #
30
31
  # @param [:named, :raw, :both] mode modifies the result
31
32
  # @return [String] key code ("as is") in `:raw` mode
32
33
  # @return [String] key name in `:named` mode
33
34
  # @return [[String, String]] key code and key name in `:both` mode
34
- # @return [nil] in any error case
35
+ # @return [nil] in error case
35
36
  def read_key(mode: :named)
36
37
  event = read_key_event or return
37
38
  return event.raw if mode == :raw
@@ -42,7 +43,7 @@ module Terminal
42
43
  # Read next {KeyEvent} from standard input.
43
44
  #
44
45
  # @return [KeyEvent] next event
45
- # @return [nil] on any error
46
+ # @return [nil] in error case
46
47
  def read_key_event
47
48
  case input_mode
48
49
  when :dumb
@@ -51,25 +52,24 @@ module Terminal
51
52
  KeyEvent.new(raw, *opts)
52
53
  when :csi_u, :legacy
53
54
  # raw = with_mouse ? read_tty_with_mouse : read_tty
54
- raw = read_tty
55
- AsKeyEvent[raw] if raw
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
72
- rescue SignalException
71
+ :legacy
72
+ rescue Interrupt
73
73
  :legacy
74
74
  rescue IOError, SystemCallError
75
75
  :error
@@ -90,7 +90,7 @@ module Terminal
90
90
  nil
91
91
  end
92
92
 
93
- def read_tty_with_mouse(any_move: false)
93
+ def read_tty_with_mouse(each_move: false)
94
94
  # highlight: '1001'
95
95
  # drag: '1002'
96
96
  # move: '1003'
@@ -98,7 +98,7 @@ module Terminal
98
98
  # sgr: '1006'
99
99
  # urxvt: '1015'
100
100
  # pixel: '1016'
101
- opts = any_move ? '1000;1003;1006;1015' : '1000;1006;1015'
101
+ opts = each_move ? '1000;1003;1006;1015' : '1000;1006;1015'
102
102
  opts = _write("\e[?#{opts}h") ? "\e[?#{opts}l" : nil
103
103
  read_tty
104
104
  ensure
@@ -106,13 +106,13 @@ module Terminal
106
106
  end
107
107
 
108
108
  def read_tty
109
- key = STDIN.getch
110
- return key if key != "\e"
111
- while (nc = STDIN.read_nonblock(1, exception: false))
112
- String === nc ? key << nc : break
109
+ if (key = STDIN.getch) == "\e"
110
+ while (nc = STDIN.read_nonblock(1, exception: false))
111
+ String === nc ? key << nc : break
112
+ end
113
113
  end
114
114
  key
115
- rescue SignalException
115
+ rescue Interrupt
116
116
  nil
117
117
  rescue IOError, SystemCallError
118
118
  @input_mode = :error
@@ -130,116 +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
- def new(raw, key = raw, modifier = 0, extra = nil)
152
- @cache ? super.freeze : @cache[raw] || (@cache[raw] = super.freeze)
153
- end
154
-
155
- def unknown(raw) = new(raw, nil)
156
- end
157
-
158
- # Event string received from standard input.
159
- # This can be a simple value like `"a"`or `"\e[24;6~"` (for Shift+Ctrl+F12).
160
- #
161
- # @return [String] received event string
162
- attr_reader :raw
163
-
164
- # Pressed key without any modifiers.
165
- # This can be a string for simple keys like `"a"` or a Symbol like `:F12`.
166
- # @return [String, Symbol] key without modifiers
167
- attr_reader :key
168
-
169
- # Modifier key code. This represents the encoded key modifier like `Shift`
170
- # or `Alt`.
171
- #
172
- # @return [Integer] modifier key code
173
- attr_reader :modifier
174
-
175
- # @comment for mouse events? attr_reader :extra
176
-
177
- # Name of the key event.
178
- # This can be a simple name like `"a"` or `"Shift+Ctrl+F12"` for combined
179
- # keys.
180
- #
181
- # @return [String] key name
182
- attr_reader :name
183
-
184
- # @attribute [r] modifier?
185
- # @return [true, false] whether a key modifier was pressed
186
- def modifier? = @modifier != 0
187
-
188
- # @attribute [r] simple?
189
- # @return [true, false] whether a simple char was pressed
190
- def simple? = @raw == @name
191
-
192
- # All pressed keys.
193
- # This is composed by all {modifier} and the {key}.
194
- #
195
- # @return [Array<Symbol, String>] all pressed keys
196
- def to_a = @ary.dup
197
-
198
- # @!visibility private
199
- def to_ary = simple? ? [@raw] : [@raw, @name]
200
-
201
- # @!visibility private
202
- def to_s = @name.dup
203
-
204
- # @!visibility private
205
- def inspect = "<#{self.class.name} #{to_ary.map(&:inspect).join(' ')}>"
206
-
207
- # @!visibility private
208
- def freeze
209
- @raw.freeze
210
- @key.freeze
211
- @extra.freeze
212
- @name.freeze
213
- super
214
- end
215
-
216
- private
217
-
218
- def initialize(raw, key, modifier, extra)
219
- @raw = raw
220
- @key = key
221
- @modifier = modifier
222
- @extra = extra
223
- @ary = MODIFIERS.filter_map { |b, n| n if modifier.allbits?(b) }
224
- @ary << key if key
225
- @name = @ary.join('+').encode(Encoding::UTF_8)
226
- end
227
-
228
- @cache = {}
229
-
230
- MODIFIERS = {
231
- 1 => :Shift,
232
- 2 => :Alt,
233
- 4 => :Ctrl,
234
- 8 => :Super,
235
- 16 => :Hyper,
236
- 32 => :Meta,
237
- 64 => :Caps,
238
- 128 => :Num
239
- }.freeze
240
- private_constant :MODIFIERS
241
- end
242
-
243
- autoload :AsKeyEvent, "#{__dir__}/input/as_key_event.rb"
244
- private_constant :AsKeyEvent
133
+ autoload :KeyEvent, "#{__dir__}/input/key_event.rb"
245
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
data/lib/terminal/text.rb CHANGED
@@ -30,7 +30,7 @@ module Terminal
30
30
  def width(str, bbcode: true)
31
31
  str = bbcode ? Ansi.unbbcode(str) : str.to_s
32
32
  return 0 if str.empty?
33
- str = str.encode(ENC) if str.encoding != ENC
33
+ str = str.encode(@encoding) if str.encoding != @encoding
34
34
  width = 0
35
35
  str.scan(WIDTH_SCANNER) do |sp, gc|
36
36
  next width += char_width(gc) if gc
@@ -113,7 +113,7 @@ module Terminal
113
113
 
114
114
  def char_width(char)
115
115
  ord = char.ord
116
- return CONTROL_CHAR_WIDTH[ord] || 2 if ord < 0x20
116
+ return @ctrlchar_width[ord] || 2 if ord < 0x20
117
117
  return 1 if char.size < 2 && ord < 0xa1
118
118
  width = CharWidth[ord]
119
119
  return @ambiguous_char_width if width == -1
@@ -124,7 +124,7 @@ module Terminal
124
124
  end
125
125
 
126
126
  def lim_pairs(snippeds, limit)
127
- line = EMPTY.dup
127
+ line = @empty.dup
128
128
  size = 0
129
129
  csi = nil
130
130
  snippeds.each do |snipped|
@@ -144,7 +144,7 @@ module Terminal
144
144
 
145
145
  if snipped == :hard_nl
146
146
  line[-1] == ' ' ? yield(line.chop, size - 1) : yield(line, size)
147
- line = EMPTY.dup
147
+ line = @empty.dup
148
148
  csi = nil
149
149
  next size = 0
150
150
  end
@@ -190,7 +190,7 @@ module Terminal
190
190
  end
191
191
 
192
192
  def pairs(snippeds)
193
- line = EMPTY.dup
193
+ line = @empty.dup
194
194
  size = 0
195
195
  csi = nil
196
196
  snippeds.each do |snipped|
@@ -208,7 +208,7 @@ module Terminal
208
208
 
209
209
  if snipped == :hard_nl
210
210
  line[-1] == ' ' ? yield(line.chop, size - 1) : yield(line, size)
211
- line = EMPTY.dup
211
+ line = @empty.dup
212
212
  csi = nil
213
213
  next size = 0
214
214
  end
@@ -229,7 +229,7 @@ module Terminal
229
229
  end
230
230
 
231
231
  def lim_lines(snippeds, limit)
232
- line = EMPTY.dup
232
+ line = @empty.dup
233
233
  size = 0
234
234
  csi = nil
235
235
  snippeds.each do |snipped|
@@ -249,7 +249,7 @@ module Terminal
249
249
 
250
250
  if snipped == :hard_nl
251
251
  yield(line[-1] == ' ' ? line.chop : line)
252
- line = EMPTY.dup
252
+ line = @empty.dup
253
253
  csi = nil
254
254
  next size = 0
255
255
  end
@@ -295,7 +295,7 @@ module Terminal
295
295
  end
296
296
 
297
297
  def lines(snippeds)
298
- line = EMPTY.dup
298
+ line = @empty.dup
299
299
  size = 0
300
300
  csi = nil
301
301
  snippeds.each do |snipped|
@@ -313,7 +313,7 @@ module Terminal
313
313
 
314
314
  if snipped == :hard_nl
315
315
  yield(line[-1] == ' ' ? line.chop : line)
316
- line = EMPTY.dup
316
+ line = @empty.dup
317
317
  csi = nil
318
318
  next size = 0
319
319
  end
@@ -342,7 +342,7 @@ module Terminal
342
342
  next ret << (last = :hard_nl)
343
343
  end
344
344
 
345
- txt = txt.encode(ENC) if txt.encoding != ENC
345
+ txt = txt.encode(@encoding) if txt.encoding != @encoding
346
346
 
347
347
  txt.scan(SCAN_EXPR) do |nl, csi, osc, space, gc|
348
348
  if gc
@@ -445,10 +445,8 @@ module Terminal
445
445
  private_constant :Osc, :CsiEnd, :Csi, :Word, :WordEx
446
446
 
447
447
  @ambiguous_char_width = 1
448
-
449
- ENC = Encoding::UTF_8
450
- EMPTY = String.new(encoding: ENC).freeze
451
- private_constant :ENC, :EMPTY
448
+ @encoding = Encoding::UTF_8
449
+ @empty = String.new(encoding: @encoding).freeze
452
450
 
453
451
  SCAN_EXPR =
454
452
  /\G(?:
@@ -468,7 +466,7 @@ module Terminal
468
466
  )/x
469
467
  private_constant :SCAN_EXPR, :WIDTH_SCANNER
470
468
 
471
- CONTROL_CHAR_WIDTH = {
469
+ @ctrlchar_width = {
472
470
  0x00 => 0,
473
471
  0x01 => 1,
474
472
  0x02 => 1,
@@ -502,7 +500,6 @@ module Terminal
502
500
  0x1e => 1,
503
501
  0x1f => 1
504
502
  }.compare_by_identity.freeze
505
- private_constant :CONTROL_CHAR_WIDTH
506
503
 
507
504
  autoload :CharWidth, "#{__dir__}/text/char_width.rb"
508
505
  private_constant :CharWidth
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Terminal
4
4
  # The version number of the gem.
5
- VERSION = '0.12.0'
5
+ VERSION = '0.12.2'
6
6
  end
data/lib/terminal.rb CHANGED
@@ -11,16 +11,16 @@ require_relative 'terminal/input'
11
11
  # It automagically detects whether your terminal supports ANSI features, like
12
12
  # coloring (see {colors}) or the
13
13
  # [CSIu protocol](https://sw.kovidgoyal.net/kitty/keyboard-protocol) support
14
- # (see {read_key} and {input_mode}). It calculates the display width for Unicode
15
- # chars (see {Text.width}) and help you to display text with line formatting
16
- # (see {Text.each_line}).
14
+ # (see {read_key_event} and {input_mode}).
15
+ # It calculates the display width for Unicode chars (see {Text.width}) and help
16
+ # you to display text with line formatting (see {Text.each_line}).
17
17
  #
18
18
  module Terminal
19
19
  class << self
20
- # Return true when the current terminal supports ANSI control codes.
21
- # When the terminal does not support it, {colors} will return `2` as color
22
- # count and all output methods ({<<}, {print}, {puts}) will not forward
23
- # ANSI control codes to the terminal, {read_key} will not support CSIu.
20
+ # Return true if the current terminal supports ANSI control codes.
21
+ # When the terminal does not support it, {colors} will return `2` and all
22
+ # output methods ({<<}, {print}, {puts}) will not forward ANSI control
23
+ # codes to the terminal, {read_key_event} will not support CSIu.
24
24
  #
25
25
  # @attribute [r] ansi?
26
26
  # @return [Boolean] whether ANSI control codes are supported
@@ -123,8 +123,9 @@ module Terminal
123
123
 
124
124
  # Screen size as a tuple of {rows} and {columns}.
125
125
  #
126
- # If the terminal does not support the report for it's dimension or ANSI
127
- # is not supported in general then environment variables will be used.
126
+ # If the terminal does not support the report of it's dimension or ANSI
127
+ # is not supported in general then environment variables `COLUMNS` and
128
+ # `LINES` will be used.
128
129
  # If this failed `[25, 80]` will be returned as default.
129
130
  #
130
131
  # Setting the terminal size is not widely supported.
@@ -156,7 +157,8 @@ module Terminal
156
157
  # Hide the cursor.
157
158
  # Will not send the control code if the cursor is already hidden.
158
159
  #
159
- # To show the cursor again call {show_cursor}.
160
+ # When you called {hide_cursor} n-times you need to call {show_cursor}
161
+ # n-times to show the cursor again.
160
162
  #
161
163
  # @return [Terminal] itself
162
164
  def hide_cursor
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.0
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
@@ -44,11 +43,11 @@ homepage: https://codeberg.org/mblumtritt/Terminal.rb
44
43
  licenses:
45
44
  - MIT
46
45
  metadata:
47
- rubygems_mfa_required: 'true'
48
- yard.run: yard
49
46
  source_code_uri: https://codeberg.org/mblumtritt/Terminal.rb
50
47
  bug_tracker_uri: https://codeberg.org/mblumtritt/Terminal.rb/issues
51
- documentation_uri: https://rubydoc.info/gems/terminal_rb/Terminal
48
+ documentation_uri: https://rubydoc.info/gems/terminal_rb
49
+ rubygems_mfa_required: 'true'
50
+ yard.run: yard
52
51
  rdoc_options: []
53
52
  require_paths:
54
53
  - lib
@@ -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: []