Skip to content

Commit e661aff

Browse files
committed
Unite key bindings, key mapping and multibyte buffer
1 parent d60f1e1 commit e661aff

19 files changed

+291
-452
lines changed

lib/reline.rb

Lines changed: 42 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,9 @@ module Reline
1717

1818
class ConfigEncodingConversionError < StandardError; end
1919

20-
Key = Struct.new(:char, :combined_char, :with_meta) do
21-
def match?(other)
22-
case other
23-
when Reline::Key
24-
(other.char.nil? or char.nil? or char == other.char) and
25-
(other.combined_char.nil? or combined_char.nil? or combined_char == other.combined_char) and
26-
(other.with_meta.nil? or with_meta.nil? or with_meta == other.with_meta)
27-
when Integer, Symbol
28-
(combined_char and combined_char == other) or
29-
(combined_char.nil? and char and char == other)
30-
else
31-
false
32-
end
20+
Key = Struct.new(:char, :method_symbol, :bytes) do
21+
def match?(sym)
22+
method_symbol == sym
3323
end
3424
alias_method :==, :match?
3525
end
@@ -349,24 +339,22 @@ def readline(prompt = '', add_hist = false)
349339
begin
350340
line_editor.set_signal_handlers
351341
loop do
352-
read_io(config.keyseq_timeout) { |inputs|
353-
line_editor.set_pasting_state(io_gate.in_pasting?)
354-
inputs.each do |key|
355-
if key.char == :bracketed_paste_start
356-
text = io_gate.read_bracketed_paste
357-
line_editor.insert_pasted_text(text)
358-
line_editor.scroll_into_view
359-
else
360-
line_editor.update(key)
361-
end
362-
end
342+
wait_occurs = false
343+
key = read_io(config.keyseq_timeout) {
344+
line_editor.set_pasting_state(false)
345+
wait_occurs = true
346+
line_editor.rerender
363347
}
348+
line_editor.set_pasting_state(!wait_occurs && io_gate.in_pasting?)
349+
if key.method_symbol == :bracketed_paste_start
350+
line_editor.insert_pasted_text(io_gate.read_bracketed_paste)
351+
line_editor.scroll_into_view
352+
else
353+
line_editor.update(key)
354+
end
364355
if line_editor.finished?
365356
line_editor.render_finished
366357
break
367-
else
368-
line_editor.set_pasting_state(io_gate.in_pasting?)
369-
line_editor.rerender
370358
end
371359
end
372360
io_gate.move_cursor_column(0)
@@ -378,92 +366,41 @@ def readline(prompt = '', add_hist = false)
378366
end
379367
end
380368

381-
# GNU Readline waits for "keyseq-timeout" milliseconds to see if the ESC
382-
# is followed by a character, and times out and treats it as a standalone
383-
# ESC if the second character does not arrive. If the second character
384-
# comes before timed out, it is treated as a modifier key with the
385-
# meta-property of meta-key, so that it can be distinguished from
386-
# multibyte characters with the 8th bit turned on.
387-
#
388-
# GNU Readline will wait for the 2nd character with "keyseq-timeout"
389-
# milli-seconds but wait forever after 3rd characters.
369+
# GNU Readline waits for "keyseq-timeout" milliseconds when the input is ambiguous whether it is matching or matched.
370+
# For example, ESC can be a standalone ESC or part of a sequence that starts with ESC.
371+
# GNU Readline also waits for any other amibugous keybindings defined in inputrc.
390372
private def read_io(keyseq_timeout, &block)
391373
buffer = []
374+
prev_status = :matching
392375
loop do
393-
c = io_gate.getc(Float::INFINITY)
394-
if c == -1
395-
result = :unmatched
376+
timeout = prev_status == :matching_matched ? keyseq_timeout.fdiv(1000) : Float::INFINITY
377+
block.call unless io_gate.in_pasting?
378+
c = io_gate.getc(timeout)
379+
if c == -1 || c.nil?
380+
if prev_status != :matching_matched
381+
# Input closed
382+
return Reline::Key.new(nil, nil, [])
383+
end
384+
status = :matched
396385
else
397386
buffer << c
398-
result = key_stroke.match_status(buffer)
387+
status = key_stroke.match_status(buffer)
399388
end
400-
case result
401-
when :matched
402-
expanded = key_stroke.expand(buffer).map{ |expanded_c|
403-
Reline::Key.new(expanded_c, expanded_c, false)
404-
}
405-
block.(expanded)
406-
break
407-
when :matching
408-
if buffer.size == 1
409-
case read_2nd_character_of_key_sequence(keyseq_timeout, buffer, c, block)
410-
when :break then break
411-
when :next then next
412-
end
389+
if status == :matched || (prev_status != :unmatched && status == :unmatched)
390+
expanded, bytes, rest = key_stroke.expand(buffer)
391+
if expanded.is_a?(Array)
392+
rest = expanded + rest
413393
end
414-
when :unmatched
415-
if buffer.size == 1 and c == "\e".ord
416-
read_escaped_key(keyseq_timeout, c, block)
417-
else
418-
expanded = buffer.map{ |expanded_c|
419-
Reline::Key.new(expanded_c, expanded_c, false)
420-
}
421-
block.(expanded)
394+
rest.reverse_each { |c| io_gate.ungetc(c) }
395+
buffer = []
396+
case expanded
397+
when Symbol
398+
return Reline::Key.new(bytes.last, expanded, bytes)
399+
when String
400+
return Reline::Key.new(expanded, :ed_insert, bytes)
422401
end
423-
break
424402
end
425-
end
426-
end
427-
428-
private def read_2nd_character_of_key_sequence(keyseq_timeout, buffer, c, block)
429-
succ_c = io_gate.getc(keyseq_timeout.fdiv(1000))
430-
if succ_c
431-
case key_stroke.match_status(buffer.dup.push(succ_c))
432-
when :unmatched
433-
if c == "\e".ord
434-
block.([Reline::Key.new(succ_c, succ_c | 0b10000000, true)])
435-
else
436-
block.([Reline::Key.new(c, c, false), Reline::Key.new(succ_c, succ_c, false)])
437-
end
438-
return :break
439-
when :matching
440-
io_gate.ungetc(succ_c)
441-
return :next
442-
when :matched
443-
buffer << succ_c
444-
expanded = key_stroke.expand(buffer).map{ |expanded_c|
445-
Reline::Key.new(expanded_c, expanded_c, false)
446-
}
447-
block.(expanded)
448-
return :break
449-
end
450-
else
451-
block.([Reline::Key.new(c, c, false)])
452-
return :break
453-
end
454-
end
455-
456-
private def read_escaped_key(keyseq_timeout, c, block)
457-
escaped_c = io_gate.getc(keyseq_timeout.fdiv(1000))
458-
459-
if escaped_c.nil?
460-
block.([Reline::Key.new(c, c, false)])
461-
elsif escaped_c >= 128 # maybe, first byte of multi byte
462-
block.([Reline::Key.new(c, c, false), Reline::Key.new(escaped_c, escaped_c, false)])
463-
elsif escaped_c == "\e".ord # escape twice
464-
block.([Reline::Key.new(c, c, false), Reline::Key.new(c, c, false)])
465-
else
466-
block.([Reline::Key.new(escaped_c, escaped_c | 0b10000000, true)])
403+
prev_status = status
467404
end
468405
end
469406

@@ -547,7 +484,7 @@ def self.encoding_system_needs
547484
def self.core
548485
@core ||= Core.new { |core|
549486
core.config = Reline::Config.new
550-
core.key_stroke = Reline::KeyStroke.new(core.config)
487+
core.key_stroke = Reline::KeyStroke.new(core.config, core.encoding)
551488
core.line_editor = Reline::LineEditor.new(core.config, core.encoding)
552489

553490
core.basic_word_break_characters = " \t\n`><=;|&{("

lib/reline/config.rb

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,20 @@ class InvalidInputrc < RuntimeError
3030

3131
def initialize
3232
@additional_key_bindings = {} # from inputrc
33-
@additional_key_bindings[:emacs] = {}
34-
@additional_key_bindings[:vi_insert] = {}
35-
@additional_key_bindings[:vi_command] = {}
36-
@oneshot_key_bindings = {}
33+
@additional_key_bindings[:emacs] = Reline::KeyActor::Base.new
34+
@additional_key_bindings[:vi_insert] = Reline::KeyActor::Base.new
35+
@additional_key_bindings[:vi_command] = Reline::KeyActor::Base.new
36+
@oneshot_key_bindings = Reline::KeyActor::Base.new
3737
@editing_mode_label = :emacs
3838
@keymap_label = :emacs
3939
@keymap_prefix = []
4040
@key_actors = {}
41-
@key_actors[:emacs] = Reline::KeyActor::Emacs.new
42-
@key_actors[:vi_insert] = Reline::KeyActor::ViInsert.new
43-
@key_actors[:vi_command] = Reline::KeyActor::ViCommand.new
41+
@key_actors[:emacs] = Reline::KeyActor::Base.new
42+
@key_actors[:vi_insert] = Reline::KeyActor::Base.new
43+
@key_actors[:vi_command] = Reline::KeyActor::Base.new
44+
@key_actors[:emacs].add_mappings(Reline::KeyActor::EMACS_MAPPING)
45+
@key_actors[:vi_insert].add_mappings(Reline::KeyActor::VI_INSERT_MAPPING)
46+
@key_actors[:vi_command].add_mappings(Reline::KeyActor::VI_COMMAND_MAPPING)
4447
@vi_cmd_mode_string = '(cmd)'
4548
@vi_ins_mode_string = '(ins)'
4649
@emacs_mode_string = '@'
@@ -62,7 +65,7 @@ def reset
6265
end
6366

6467
def editing_mode
65-
@key_actors[@editing_mode_label]
68+
@editing_mode_label
6669
end
6770

6871
def editing_mode=(val)
@@ -73,10 +76,6 @@ def editing_mode_is?(*val)
7376
val.any?(@editing_mode_label)
7477
end
7578

76-
def keymap
77-
@key_actors[@keymap_label]
78-
end
79-
8079
def loaded?
8180
@loaded
8281
end
@@ -133,26 +132,26 @@ def read(file = nil)
133132

134133
def key_bindings
135134
# The key bindings for each editing mode will be overwritten by the user-defined ones.
136-
kb = @key_actors[@editing_mode_label].default_key_bindings.dup
137-
kb.merge!(@additional_key_bindings[@editing_mode_label])
138-
kb.merge!(@oneshot_key_bindings)
139-
kb
135+
Reline::KeyActor::Composite.new([@oneshot_key_bindings, @additional_key_bindings[@editing_mode_label], @key_actors[@editing_mode_label]])
140136
end
141137

142138
def add_oneshot_key_binding(keystroke, target)
143-
@oneshot_key_bindings[keystroke] = target
139+
# Old IRB sets invalid keystroke [Reline::Key]. We should ignore it.
140+
return unless keystroke.all? { |c| c.is_a?(Integer) }
141+
142+
@oneshot_key_bindings.add(keystroke, target)
144143
end
145144

146145
def reset_oneshot_key_bindings
147146
@oneshot_key_bindings.clear
148147
end
149148

150149
def add_default_key_binding_by_keymap(keymap, keystroke, target)
151-
@key_actors[keymap].default_key_bindings[keystroke] = target
150+
@key_actors[keymap].add(keystroke, target)
152151
end
153152

154153
def add_default_key_binding(keystroke, target)
155-
@key_actors[@keymap_label].default_key_bindings[keystroke] = target
154+
@key_actors[@keymap_label].add(keystroke, target)
156155
end
157156

158157
def read_lines(lines, file = nil)
@@ -192,7 +191,7 @@ def read_lines(lines, file = nil)
192191
func_name = func_name.split.first
193192
keystroke, func = bind_key(key, func_name)
194193
next unless keystroke
195-
@additional_key_bindings[@keymap_label][@keymap_prefix + keystroke] = func
194+
@additional_key_bindings[@keymap_label].add(@keymap_prefix + keystroke, func)
196195
end
197196
end
198197
unless if_stack.empty?

lib/reline/key_actor.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ module Reline::KeyActor
22
end
33

44
require 'reline/key_actor/base'
5+
require 'reline/key_actor/composite'
56
require 'reline/key_actor/emacs'
67
require 'reline/key_actor/vi_command'
78
require 'reline/key_actor/vi_insert'

lib/reline/key_actor/base.rb

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,36 @@
11
class Reline::KeyActor::Base
2-
MAPPING = Array.new(256)
2+
def initialize
3+
@matching_bytes = {}
4+
@key_bindings = {}
5+
end
36

4-
def get_method(key)
5-
self.class::MAPPING[key]
7+
def add_mappings(mappings)
8+
add([27], :ed_ignore)
9+
128.times do |key|
10+
func = mappings[key]
11+
meta_func = mappings[key | 0b10000000]
12+
add([key], func) unless func == :ed_unassigned
13+
add([27, key], meta_func) unless meta_func == :ed_unassigned
14+
end
615
end
716

8-
def initialize
9-
@default_key_bindings = {}
17+
def add(key, func)
18+
(1...key.size).each do |size|
19+
@matching_bytes[key.take(size)] = true
20+
end
21+
@key_bindings[key] = func
22+
end
23+
24+
def matching?(key)
25+
@matching_bytes[key]
26+
end
27+
28+
def get(key)
29+
@key_bindings[key]
1030
end
1131

12-
def default_key_bindings
13-
@default_key_bindings
32+
def clear
33+
@matching_bytes.clear
34+
@key_bindings.clear
1435
end
1536
end

lib/reline/key_actor/composite.rb

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
class Reline::KeyActor::Composite
2+
def initialize(key_actors)
3+
@key_actors = key_actors
4+
end
5+
6+
def matching?(key)
7+
@key_actors.any? { |key_actor| key_actor.matching?(key) }
8+
end
9+
10+
def get(key)
11+
@key_actors.each do |key_actor|
12+
func = key_actor.get(key)
13+
return func if func
14+
end
15+
nil
16+
end
17+
end

lib/reline/key_actor/emacs.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
class Reline::KeyActor::Emacs < Reline::KeyActor::Base
2-
MAPPING = [
1+
module Reline::KeyActor
2+
EMACS_MAPPING = [
33
# 0 ^@
44
:em_set_mark,
55
# 1 ^A

lib/reline/key_actor/vi_command.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
class Reline::KeyActor::ViCommand < Reline::KeyActor::Base
2-
MAPPING = [
1+
module Reline::KeyActor
2+
VI_COMMAND_MAPPING = [
33
# 0 ^@
44
:ed_unassigned,
55
# 1 ^A

lib/reline/key_actor/vi_insert.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
class Reline::KeyActor::ViInsert < Reline::KeyActor::Base
2-
MAPPING = [
1+
module Reline::KeyActor
2+
VI_INSERT_MAPPING = [
33
# 0 ^@
44
:ed_unassigned,
55
# 1 ^A

0 commit comments

Comments
 (0)