Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge key mapping with key bindings #715

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions lib/reline.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ module Reline

class ConfigEncodingConversionError < StandardError; end

Key = Struct.new(:char, :combined_char, :with_meta) do
# EOF key: { char: nil, method_symbol: nil }
# Other key: { char: String, method_symbol: Symbol }
Key = Struct.new(:char, :method_symbol, :unused_boolean) do
# For dialog_proc `key.match?(dialog.name)`
def match?(sym)
combined_char.is_a?(Symbol) && combined_char == sym
method_symbol && method_symbol == sym
end
end
CursorPos = Struct.new(:x, :y)
Expand Down Expand Up @@ -341,7 +343,7 @@ def readline(prompt = '', add_hist = false)
read_io(config.keyseq_timeout) { |inputs|
line_editor.set_pasting_state(io_gate.in_pasting?)
inputs.each do |key|
if key.char == :bracketed_paste_start
if key.method_symbol == :bracketed_paste_start
text = io_gate.read_bracketed_paste
line_editor.insert_pasted_text(text)
line_editor.scroll_into_view
Expand Down Expand Up @@ -483,7 +485,7 @@ def self.encoding_system_needs
def self.core
@core ||= Core.new { |core|
core.config = Reline::Config.new
core.key_stroke = Reline::KeyStroke.new(core.config)
core.key_stroke = Reline::KeyStroke.new(core.config, core.encoding)
core.line_editor = Reline::LineEditor.new(core.config, core.encoding)

core.basic_word_break_characters = " \t\n`><=;|&{("
Expand Down
14 changes: 10 additions & 4 deletions lib/reline/key_actor/base.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
class Reline::KeyActor::Base
def initialize(mapping = [])
@mapping = mapping
def initialize(mappings = nil)
@matching_bytes = {}
@key_bindings = {}
add_mappings(mappings) if mappings
end

def get_method(key)
@mapping[key]
def add_mappings(mappings)
add([27], :ed_ignore)
128.times do |key|
func = mappings[key]
meta_func = mappings[key | 0b10000000]
add([key], func) unless func == :ed_unassigned
add([27, key], meta_func) unless meta_func == :ed_unassigned
end
end

def add(key, func)
Expand Down
40 changes: 24 additions & 16 deletions lib/reline/key_stroke.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ class Reline::KeyStroke
CSI_PARAMETER_BYTES_RANGE = 0x30..0x3f
CSI_INTERMEDIATE_BYTES_RANGE = (0x20..0x2f)

def initialize(config)
def initialize(config, encoding)
@config = config
@encoding = encoding
end

# Input exactly matches to a key sequence
Expand All @@ -19,11 +20,6 @@ def initialize(config)
def match_status(input)
matching = key_mapping.matching?(input)
matched = key_mapping.get(input)

# FIXME: Workaround for single byte. remove this after MAPPING is merged into KeyActor.
matched ||= input.size == 1
matching ||= input == [ESC_BYTE]

if matching && matched
MATCHING_MATCHED
elsif matching
Expand All @@ -32,10 +28,14 @@ def match_status(input)
MATCHED
elsif input[0] == ESC_BYTE
match_unknown_escape_sequence(input, vi_mode: @config.editing_mode_is?(:vi_insert, :vi_command))
elsif input.size == 1
MATCHED
else
UNMATCHED
s = input.pack('c*').force_encoding(@encoding)
if s.valid_encoding?
s.size == 1 ? MATCHED : UNMATCHED
else
# Invalid string is MATCHING (part of valid string) or MATCHED (invalid bytes to be ignored)
MATCHING_MATCHED
end
end
end

Expand All @@ -45,20 +45,28 @@ def expand(input)
bytes = input.take(i)
status = match_status(bytes)
matched_bytes = bytes if status == MATCHED || status == MATCHING_MATCHED
break if status == MATCHED || status == UNMATCHED
end
return [[], []] unless matched_bytes

func = key_mapping.get(matched_bytes)
s = matched_bytes.pack('c*').force_encoding(@encoding)
if func.is_a?(Array)
keys = func.map { |c| Reline::Key.new(c, c, false) }
# Perform simple macro expansion for single byte key bindings.
# Multibyte key bindings and recursive macro expansion are not supported yet.
marco = func.pack('c*').force_encoding(@encoding)
keys = marco.chars.map do |c|
f = key_mapping.get(c.bytes)
Reline::Key.new(c, f.is_a?(Symbol) ? f : :ed_insert, false)
end
elsif func
keys = [Reline::Key.new(func, func, false)]
elsif matched_bytes.size == 1
keys = [Reline::Key.new(matched_bytes.first, matched_bytes.first, false)]
elsif matched_bytes.size == 2 && matched_bytes[0] == ESC_BYTE
keys = [Reline::Key.new(matched_bytes[1], matched_bytes[1] | 0b10000000, true)]
keys = [Reline::Key.new(s, func, false)]
else
keys = []
if s.valid_encoding? && s.size == 1
keys = [Reline::Key.new(s, :ed_insert, false)]
else
keys = []
end
end

[keys, input.drop(matched_bytes.size)]
Expand Down
Loading
Loading