reline 0.1.1 → 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bb3a343a202a2c043257a432f50eb05eeb58b5638a88c24eb095c96a40b03fea
4
- data.tar.gz: e9c8aec5f70fe3897fb8f835d0d5b228a93a5444b0a7ce804290c19ae44c6c55
3
+ metadata.gz: '0259fdcb2bf55d2b5532e0da9259247dbee06eefc4564e664006218a094d140f'
4
+ data.tar.gz: ea484914d377faed8c7b346fce10a94e996661950714bac68bf8c604a4bd492b
5
5
  SHA512:
6
- metadata.gz: a0e2a92e1e007d23542313783108250c66ad81b90389c306a9f413b8426ce14a39e14731f974aa8fcabcddaeceae529a70388e6e1a3db6ec7a511278b0c568d7
7
- data.tar.gz: 0f86f497938921154a475f4b63059a4cda560b44b5b6f9fb63b3b4afcae9de3ea351c1226640eb074546a765b2f51ff5a62bd1af20142a6211c8e74a06f16a57
6
+ metadata.gz: f108f900ffb86ad334c82f6a64777e78b32e010985f9add33f4ad6ae55f7f1890ed2b16d250d02388ba9b8bc8aed3e985241e3f95b4e43b0d6fe2b9c0ee28057
7
+ data.tar.gz: eb45c06febe94e7f953f24db1be2375e619c0fb3d270de7dddc3056c0a473438de2c528e78f6d6682b0a35dcd84d901845aba72c0a16346109b3f84de7276702
data/README.md CHANGED
@@ -1,5 +1,9 @@
1
1
  [![Build Status](https://travis-ci.com/ruby/reline.svg?branch=master)](https://travis-ci.com/ruby/reline)
2
2
 
3
+ This is a screen capture of *IRB improved by Reline*.
4
+
5
+ ![IRB improved by Reline](https://raw.githubusercontent.com/wiki/ruby/reline/images/irb_improved_by_reline.gif)
6
+
3
7
  # Reline
4
8
 
5
9
  Reline is compatible with the API of Ruby's stdlib 'readline', GNU Readline and Editline by pure Ruby implementation.
@@ -7,6 +7,7 @@ require 'reline/key_actor'
7
7
  require 'reline/key_stroke'
8
8
  require 'reline/line_editor'
9
9
  require 'reline/history'
10
+ require 'rbconfig'
10
11
 
11
12
  module Reline
12
13
  FILENAME_COMPLETION_PROC = nil
@@ -45,40 +46,44 @@ module Reline
45
46
  @completion_quote_character = nil
46
47
  end
47
48
 
49
+ def encoding
50
+ Reline::IOGate.encoding
51
+ end
52
+
48
53
  def completion_append_character=(val)
49
54
  if val.nil?
50
55
  @completion_append_character = nil
51
56
  elsif val.size == 1
52
- @completion_append_character = val.encode(Encoding::default_external)
57
+ @completion_append_character = val.encode(Reline::IOGate.encoding)
53
58
  elsif val.size > 1
54
- @completion_append_character = val[0].encode(Encoding::default_external)
59
+ @completion_append_character = val[0].encode(Reline::IOGate.encoding)
55
60
  else
56
61
  @completion_append_character = nil
57
62
  end
58
63
  end
59
64
 
60
65
  def basic_word_break_characters=(v)
61
- @basic_word_break_characters = v.encode(Encoding::default_external)
66
+ @basic_word_break_characters = v.encode(Reline::IOGate.encoding)
62
67
  end
63
68
 
64
69
  def completer_word_break_characters=(v)
65
- @completer_word_break_characters = v.encode(Encoding::default_external)
70
+ @completer_word_break_characters = v.encode(Reline::IOGate.encoding)
66
71
  end
67
72
 
68
73
  def basic_quote_characters=(v)
69
- @basic_quote_characters = v.encode(Encoding::default_external)
74
+ @basic_quote_characters = v.encode(Reline::IOGate.encoding)
70
75
  end
71
76
 
72
77
  def completer_quote_characters=(v)
73
- @completer_quote_characters = v.encode(Encoding::default_external)
78
+ @completer_quote_characters = v.encode(Reline::IOGate.encoding)
74
79
  end
75
80
 
76
81
  def filename_quote_characters=(v)
77
- @filename_quote_characters = v.encode(Encoding::default_external)
82
+ @filename_quote_characters = v.encode(Reline::IOGate.encoding)
78
83
  end
79
84
 
80
85
  def special_prefixes=(v)
81
- @special_prefixes = v.encode(Encoding::default_external)
86
+ @special_prefixes = v.encode(Reline::IOGate.encoding)
82
87
  end
83
88
 
84
89
  def completion_case_fold=(v)
@@ -94,22 +99,22 @@ module Reline
94
99
  end
95
100
 
96
101
  def completion_proc=(p)
97
- raise ArgumentError unless p.respond_to?(:call)
102
+ raise ArgumentError unless p.respond_to?(:call) or p.nil?
98
103
  @completion_proc = p
99
104
  end
100
105
 
101
106
  def output_modifier_proc=(p)
102
- raise ArgumentError unless p.respond_to?(:call)
107
+ raise ArgumentError unless p.respond_to?(:call) or p.nil?
103
108
  @output_modifier_proc = p
104
109
  end
105
110
 
106
111
  def prompt_proc=(p)
107
- raise ArgumentError unless p.respond_to?(:call)
112
+ raise ArgumentError unless p.respond_to?(:call) or p.nil?
108
113
  @prompt_proc = p
109
114
  end
110
115
 
111
116
  def auto_indent_proc=(p)
112
- raise ArgumentError unless p.respond_to?(:call)
117
+ raise ArgumentError unless p.respond_to?(:call) or p.nil?
113
118
  @auto_indent_proc = p
114
119
  end
115
120
 
@@ -118,7 +123,7 @@ module Reline
118
123
  end
119
124
 
120
125
  def dig_perfect_match_proc=(p)
121
- raise ArgumentError unless p.respond_to?(:call)
126
+ raise ArgumentError unless p.respond_to?(:call) or p.nil?
122
127
  @dig_perfect_match_proc = p
123
128
  end
124
129
 
@@ -171,7 +176,7 @@ module Reline
171
176
 
172
177
  whole_buffer = line_editor.whole_buffer.dup
173
178
  whole_buffer.taint if RUBY_VERSION < '2.7'
174
- if add_hist and whole_buffer and whole_buffer.chomp.size > 0
179
+ if add_hist and whole_buffer and whole_buffer.chomp("\n").size > 0
175
180
  Reline::HISTORY << whole_buffer
176
181
  end
177
182
 
@@ -184,8 +189,8 @@ module Reline
184
189
 
185
190
  line = line_editor.line.dup
186
191
  line.taint if RUBY_VERSION < '2.7'
187
- if add_hist and line and line.chomp.size > 0
188
- Reline::HISTORY << line.chomp
192
+ if add_hist and line and line.chomp("\n").size > 0
193
+ Reline::HISTORY << line.chomp("\n")
189
194
  end
190
195
 
191
196
  line_editor.reset_line if line_editor.line.nil?
@@ -201,7 +206,7 @@ module Reline
201
206
  otio = Reline::IOGate.prep
202
207
 
203
208
  may_req_ambiguous_char_width
204
- line_editor.reset(prompt)
209
+ line_editor.reset(prompt, encoding: Reline::IOGate.encoding)
205
210
  if multiline
206
211
  line_editor.multiline_on
207
212
  if block_given?
@@ -218,7 +223,6 @@ module Reline
218
223
  line_editor.auto_indent_proc = auto_indent_proc
219
224
  line_editor.dig_perfect_match_proc = dig_perfect_match_proc
220
225
  line_editor.pre_input_hook = pre_input_hook
221
- line_editor.rerender
222
226
 
223
227
  unless config.test_mode
224
228
  config.read
@@ -228,17 +232,27 @@ module Reline
228
232
  end
229
233
  end
230
234
 
235
+ line_editor.rerender
236
+
231
237
  begin
238
+ prev_pasting_state = false
232
239
  loop do
240
+ prev_pasting_state = Reline::IOGate.in_pasting?
233
241
  read_io(config.keyseq_timeout) { |inputs|
234
242
  inputs.each { |c|
235
243
  line_editor.input_key(c)
236
244
  line_editor.rerender
237
245
  }
238
246
  }
247
+ if prev_pasting_state == true and not Reline::IOGate.in_pasting? and not line_editor.finished?
248
+ prev_pasting_state = false
249
+ line_editor.rerender_all
250
+ end
239
251
  break if line_editor.finished?
240
252
  end
241
253
  Reline::IOGate.move_cursor_column(0)
254
+ rescue Errno::EIO
255
+ # Maybe the I/O has been closed.
242
256
  rescue StandardError => e
243
257
  line_editor.finalize
244
258
  Reline::IOGate.deprep(otio)
@@ -332,8 +346,14 @@ module Reline
332
346
  @ambiguous_width = 2 if Reline::IOGate == Reline::GeneralIO or STDOUT.is_a?(File)
333
347
  return if ambiguous_width
334
348
  Reline::IOGate.move_cursor_column(0)
335
- print "\u{25bd}"
336
- @ambiguous_width = Reline::IOGate.cursor_pos.x
349
+ begin
350
+ output.write "\u{25bd}"
351
+ rescue Encoding::UndefinedConversionError
352
+ # LANG=C
353
+ @ambiguous_width = 1
354
+ else
355
+ @ambiguous_width = Reline::IOGate.cursor_pos.x
356
+ end
337
357
  Reline::IOGate.move_cursor_column(0)
338
358
  Reline::IOGate.erase_after_cursor
339
359
  end
@@ -387,11 +407,15 @@ module Reline
387
407
  def_instance_delegators self, :readmultiline
388
408
  private :readmultiline
389
409
 
410
+ def self.encoding_system_needs
411
+ self.core.encoding
412
+ end
413
+
390
414
  def self.core
391
415
  @core ||= Core.new { |core|
392
416
  core.config = Reline::Config.new
393
417
  core.key_stroke = Reline::KeyStroke.new(core.config)
394
- core.line_editor = Reline::LineEditor.new(core.config)
418
+ core.line_editor = Reline::LineEditor.new(core.config, Reline::IOGate.encoding)
395
419
 
396
420
  core.basic_word_break_characters = " \t\n`><=;|&{("
397
421
  core.completer_word_break_characters = " \t\n`><=;|&{("
@@ -405,14 +429,11 @@ module Reline
405
429
  def self.line_editor
406
430
  core.line_editor
407
431
  end
408
-
409
- HISTORY = History.new(core.config)
410
432
  end
411
433
 
412
434
  if RbConfig::CONFIG['host_os'] =~ /mswin|msys|mingw|cygwin|bccwin|wince|emc/
413
435
  require 'reline/windows'
414
- if Reline::Windows.get_screen_size == [0, 0]
415
- # Maybe Mintty on Cygwin
436
+ if Reline::Windows.msys_tty?
416
437
  require 'reline/ansi'
417
438
  Reline::IOGate = Reline::ANSI
418
439
  else
@@ -422,4 +443,5 @@ else
422
443
  require 'reline/ansi'
423
444
  Reline::IOGate = Reline::ANSI
424
445
  end
446
+ Reline::HISTORY = Reline::History.new(Reline.core.config)
425
447
  require 'reline/general_io'
@@ -1,20 +1,59 @@
1
1
  require 'io/console'
2
2
 
3
3
  class Reline::ANSI
4
+ def self.encoding
5
+ Encoding.default_external
6
+ end
7
+
8
+ def self.win?
9
+ false
10
+ end
11
+
4
12
  RAW_KEYSTROKE_CONFIG = {
13
+ # Console (80x25)
14
+ [27, 91, 49, 126] => :ed_move_to_beg, # Home
15
+ [27, 91, 52, 126] => :ed_move_to_end, # End
16
+ [27, 91, 51, 126] => :key_delete, # Del
5
17
  [27, 91, 65] => :ed_prev_history, # ↑
6
18
  [27, 91, 66] => :ed_next_history, # ↓
7
19
  [27, 91, 67] => :ed_next_char, # →
8
20
  [27, 91, 68] => :ed_prev_char, # ←
9
- [27, 91, 51, 126] => :key_delete, # Del
10
- [27, 91, 49, 126] => :ed_move_to_beg, # Home
11
- [27, 91, 52, 126] => :ed_move_to_end, # End
21
+
22
+ # KDE
12
23
  [27, 91, 72] => :ed_move_to_beg, # Home
13
24
  [27, 91, 70] => :ed_move_to_end, # End
25
+ # Del is 0x08
26
+ [27, 71, 65] => :ed_prev_history, # ↑
27
+ [27, 71, 66] => :ed_next_history, # ↓
28
+ [27, 71, 67] => :ed_next_char, # →
29
+ [27, 71, 68] => :ed_prev_char, # ←
30
+
31
+ # urxvt / exoterm
32
+ [27, 91, 55, 126] => :ed_move_to_beg, # Home
33
+ [27, 91, 56, 126] => :ed_move_to_end, # End
34
+
35
+ # GNOME
36
+ [27, 79, 72] => :ed_move_to_beg, # Home
37
+ [27, 79, 70] => :ed_move_to_end, # End
38
+ # Del is 0x08
39
+ # Arrow keys are the same of KDE
40
+
41
+ # iTerm2
42
+ [27, 27, 91, 67] => :em_next_word, # Option+→
43
+ [27, 27, 91, 68] => :ed_prev_word, # Option+←
44
+ [195, 166] => :em_next_word, # Option+f
45
+ [195, 162] => :ed_prev_word, # Option+b
46
+
47
+ # others
14
48
  [27, 32] => :em_set_mark, # M-<space>
15
49
  [24, 24] => :em_exchange_mark, # C-x C-x TODO also add Windows
16
50
  [27, 91, 49, 59, 53, 67] => :em_next_word, # Ctrl+→
17
51
  [27, 91, 49, 59, 53, 68] => :ed_prev_word, # Ctrl+←
52
+
53
+ [27, 79, 65] => :ed_prev_history, # ↑
54
+ [27, 79, 66] => :ed_next_history, # ↓
55
+ [27, 79, 67] => :ed_next_char, # →
56
+ [27, 79, 68] => :ed_prev_char, # ←
18
57
  }
19
58
 
20
59
  @@input = STDIN
@@ -32,8 +71,29 @@ class Reline::ANSI
32
71
  unless @@buf.empty?
33
72
  return @@buf.shift
34
73
  end
35
- c = @@input.raw(intr: true, &:getbyte)
74
+ until c = @@input.raw(intr: true, &:getbyte)
75
+ sleep 0.1
76
+ end
36
77
  (c == 0x16 && @@input.raw(min: 0, tim: 0, &:getbyte)) || c
78
+ rescue Errno::EIO
79
+ # Maybe the I/O has been closed.
80
+ nil
81
+ end
82
+
83
+ def self.in_pasting?
84
+ not Reline::IOGate.empty_buffer?
85
+ end
86
+
87
+ def self.empty_buffer?
88
+ unless @@buf.empty?
89
+ return false
90
+ end
91
+ rs, = IO.select([@@input], [], [], 0.00001)
92
+ if rs and rs[0]
93
+ false
94
+ else
95
+ true
96
+ end
37
97
  end
38
98
 
39
99
  def self.ungetc(c)
@@ -41,16 +101,23 @@ class Reline::ANSI
41
101
  end
42
102
 
43
103
  def self.retrieve_keybuffer
104
+ begin
44
105
  result = select([@@input], [], [], 0.001)
45
106
  return if result.nil?
46
107
  str = @@input.read_nonblock(1024)
47
108
  str.bytes.each do |c|
48
109
  @@buf.push(c)
49
110
  end
111
+ rescue EOFError
112
+ end
50
113
  end
51
114
 
52
115
  def self.get_screen_size
53
- @@input.winsize
116
+ s = @@input.winsize
117
+ return s if s[0] > 0 && s[1] > 0
118
+ s = [ENV["LINES"].to_i, ENV["COLUMNS"].to_i]
119
+ return s if s[0] > 0 && s[1] > 0
120
+ [24, 80]
54
121
  rescue Errno::ENOTTY
55
122
  [24, 80]
56
123
  end
@@ -69,10 +136,13 @@ class Reline::ANSI
69
136
  @@input.raw do |stdin|
70
137
  @@output << "\e[6n"
71
138
  @@output.flush
72
- while (c = stdin.getc) != 'R'
73
- res << c if c
139
+ loop do
140
+ c = stdin.getc
141
+ next if c.nil?
142
+ res << c
143
+ m = res.match(/\e\[(?<row>\d+);(?<column>\d+)R/)
144
+ break if m
74
145
  end
75
- m = res.match(/\e\[(?<row>\d+);(?<column>\d+)/)
76
146
  (m.pre_match + m.post_match).chars.reverse_each do |ch|
77
147
  stdin.ungetc ch
78
148
  end
@@ -80,20 +150,27 @@ class Reline::ANSI
80
150
  column = m[:column].to_i - 1
81
151
  row = m[:row].to_i - 1
82
152
  rescue Errno::ENOTTY
83
- buf = @@output.pread(@@output.pos, 0)
84
- row = buf.count("\n")
85
- column = buf.rindex("\n") ? (buf.size - buf.rindex("\n")) - 1 : 0
153
+ begin
154
+ buf = @@output.pread(@@output.pos, 0)
155
+ row = buf.count("\n")
156
+ column = buf.rindex("\n") ? (buf.size - buf.rindex("\n")) - 1 : 0
157
+ rescue Errno::ESPIPE
158
+ # Just returns column 1 for ambiguous width because this I/O is not
159
+ # tty and can't seek.
160
+ row = 0
161
+ column = 1
162
+ end
86
163
  end
87
164
  Reline::CursorPos.new(column, row)
88
165
  end
89
166
 
90
167
  def self.move_cursor_column(x)
91
- print "\e[#{x + 1}G"
168
+ @@output.write "\e[#{x + 1}G"
92
169
  end
93
170
 
94
171
  def self.move_cursor_up(x)
95
172
  if x > 0
96
- print "\e[#{x}A" if x > 0
173
+ @@output.write "\e[#{x}A" if x > 0
97
174
  elsif x < 0
98
175
  move_cursor_down(-x)
99
176
  end
@@ -101,24 +178,24 @@ class Reline::ANSI
101
178
 
102
179
  def self.move_cursor_down(x)
103
180
  if x > 0
104
- print "\e[#{x}B" if x > 0
181
+ @@output.write "\e[#{x}B" if x > 0
105
182
  elsif x < 0
106
183
  move_cursor_up(-x)
107
184
  end
108
185
  end
109
186
 
110
187
  def self.erase_after_cursor
111
- print "\e[K"
188
+ @@output.write "\e[K"
112
189
  end
113
190
 
114
191
  def self.scroll_down(x)
115
192
  return if x.zero?
116
- print "\e[#{x}S"
193
+ @@output.write "\e[#{x}S"
117
194
  end
118
195
 
119
196
  def self.clear_screen
120
- print "\e[2J"
121
- print "\e[1;1H"
197
+ @@output.write "\e[2J"
198
+ @@output.write "\e[1;1H"
122
199
  end
123
200
 
124
201
  @@old_winch_handler = nil
@@ -1,10 +1,6 @@
1
- require 'pathname'
2
-
3
1
  class Reline::Config
4
2
  attr_reader :test_mode
5
3
 
6
- DEFAULT_PATH = '~/.inputrc'
7
-
8
4
  KEYSEQ_PATTERN = /\\(?:C|Control)-[A-Za-z_]|\\(?:M|Meta)-[0-9A-Za-z_]|\\(?:C|Control)-(?:M|Meta)-[A-Za-z_]|\\(?:M|Meta)-(?:C|Control)-[A-Za-z_]|\\e|\\[\\\"\'abdfnrtv]|\\\d{1,3}|\\x\h{1,2}|./
9
5
 
10
6
  class InvalidInputrc < RuntimeError
@@ -37,6 +33,10 @@ class Reline::Config
37
33
  show-all-if-ambiguous
38
34
  show-all-if-unmodified
39
35
  visible-stats
36
+ show-mode-in-prompt
37
+ vi-cmd-mode-icon
38
+ vi-ins-mode-icon
39
+ emacs-mode-string
40
40
  }
41
41
  VARIABLE_NAME_SYMBOLS = VARIABLE_NAMES.map { |v| :"#{v.tr(?-, ?_)}" }
42
42
  VARIABLE_NAME_SYMBOLS.each do |v|
@@ -54,7 +54,11 @@ class Reline::Config
54
54
  @key_actors[:emacs] = Reline::KeyActor::Emacs.new
55
55
  @key_actors[:vi_insert] = Reline::KeyActor::ViInsert.new
56
56
  @key_actors[:vi_command] = Reline::KeyActor::ViCommand.new
57
- @history_size = 500
57
+ @vi_cmd_mode_icon = '(cmd)'
58
+ @vi_ins_mode_icon = '(ins)'
59
+ @emacs_mode_string = '@'
60
+ # https://tiswww.case.edu/php/chet/readline/readline.html#IDX25
61
+ @history_size = -1 # unlimited
58
62
  @keyseq_timeout = 500
59
63
  @test_mode = false
60
64
  end
@@ -83,8 +87,34 @@ class Reline::Config
83
87
  @key_actors[@keymap_label]
84
88
  end
85
89
 
90
+ def inputrc_path
91
+ case ENV['INPUTRC']
92
+ when nil, ''
93
+ else
94
+ return File.expand_path(ENV['INPUTRC'])
95
+ end
96
+
97
+ # In the XDG Specification, if ~/.config/readline/inputrc exists, then
98
+ # ~/.inputrc should not be read, but for compatibility with GNU Readline,
99
+ # if ~/.inputrc exists, then it is given priority.
100
+ home_rc_path = File.expand_path('~/.inputrc')
101
+ return home_rc_path if File.exist?(home_rc_path)
102
+
103
+ case path = ENV['XDG_CONFIG_HOME']
104
+ when nil, ''
105
+ else
106
+ path = File.join(path, 'readline/inputrc')
107
+ return path if File.exist?(path) and path == File.expand_path(path)
108
+ end
109
+
110
+ path = File.expand_path('~/.config/readline/inputrc')
111
+ return path if File.exist?(path)
112
+
113
+ return home_rc_path
114
+ end
115
+
86
116
  def read(file = nil)
87
- file ||= File.expand_path(ENV['INPUTRC'] || DEFAULT_PATH)
117
+ file ||= inputrc_path
88
118
  begin
89
119
  if file.respond_to?(:readlines)
90
120
  lines = file.readlines
@@ -135,7 +165,7 @@ class Reline::Config
135
165
 
136
166
  case line
137
167
  when /^set +([^ ]+) +([^ ]+)/i
138
- var, value = $1.downcase, $2.downcase
168
+ var, value = $1.downcase, $2
139
169
  bind_variable(var, value)
140
170
  next
141
171
  when /\s*("#{KEYSEQ_PATTERN}+")\s*:\s*(.*)\s*$/o
@@ -184,9 +214,12 @@ class Reline::Config
184
214
 
185
215
  def bind_variable(name, value)
186
216
  case name
187
- when *VARIABLE_NAMES then
188
- variable_name = :"@#{name.tr(?-, ?_)}"
189
- instance_variable_set(variable_name, value.nil? || value == '1' || value == 'on')
217
+ when 'history-size'
218
+ begin
219
+ @history_size = Integer(value)
220
+ rescue ArgumentError
221
+ @history_size = 500
222
+ end
190
223
  when 'bell-style'
191
224
  @bell_style =
192
225
  case value
@@ -225,6 +258,32 @@ class Reline::Config
225
258
  end
226
259
  when 'keyseq-timeout'
227
260
  @keyseq_timeout = value.to_i
261
+ when 'show-mode-in-prompt'
262
+ case value
263
+ when 'off'
264
+ @show_mode_in_prompt = false
265
+ when 'on'
266
+ @show_mode_in_prompt = true
267
+ else
268
+ @show_mode_in_prompt = false
269
+ end
270
+ when 'vi-cmd-mode-string'
271
+ @vi_cmd_mode_icon = retrieve_string(value)
272
+ when 'vi-ins-mode-string'
273
+ @vi_ins_mode_icon = retrieve_string(value)
274
+ when 'emacs-mode-string'
275
+ @emacs_mode_string = retrieve_string(value)
276
+ when *VARIABLE_NAMES then
277
+ variable_name = :"@#{name.tr(?-, ?_)}"
278
+ instance_variable_set(variable_name, value.nil? || value == '1' || value == 'on')
279
+ end
280
+ end
281
+
282
+ def retrieve_string(str)
283
+ if str =~ /\A"(.*)"\z/
284
+ parse_keyseq($1).map(&:chr).join
285
+ else
286
+ parse_keyseq(str).map(&:chr).join
228
287
  end
229
288
  end
230
289