Skip to content

Commit

Permalink
Add key stroke.
Browse files Browse the repository at this point in the history
  • Loading branch information
osyo-manga committed Apr 8, 2019
1 parent 4a6775f commit 8bf4989
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 3 deletions.
20 changes: 17 additions & 3 deletions lib/reline.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
require 'reline/version'
require 'reline/config'
require 'reline/key_actor'
require 'reline/key_stroke'
require 'reline/line_editor'

module Reline
Expand Down Expand Up @@ -85,11 +86,24 @@ def self.readline(prompt = '', add_hist = false)
@line_editor.completion_proc = @completion_proc
@line_editor.retrieve_completion_block = method(:retrieve_completion_block)
@line_editor.rerender
config = {
key_mapping: {
# TODO
# "a" => "bb",
# "z" => "aa",
# "y" => "ak",
}
}
key_stroke = Reline::KeyStroke.new(config)
begin
while c = getc
@line_editor.input_key(c)
@line_editor.rerender
break if @line_editor.finished?
key_stroke.input_to!(c)&.then { |inputs|
inputs.each { |c|
@line_editor.input_key(c)
@line_editor.rerender
break if @line_editor.finished?
}
}
end
move_cursor_column(0)
if add_hist and @line_editor.line and @line_editor.line.chomp.size > 0
Expand Down
70 changes: 70 additions & 0 deletions lib/reline/key_stroke.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
class Reline::KeyStroke
using Module.new {
refine Array do
def start_with?(other)
other.size <= size && other == self.take(other.size)
end
end
}

def initialize(config)
@config = config
@buffer = []
end

def input_to(bytes)
case match_status(bytes)
when :matching
nil
when :matched
expand(bytes)
when :unmatched
bytes
end
end

def input_to!(bytes)
@buffer.concat Array(bytes)
input_to(@buffer)&.tap { clear }
end

private

def match_status(input)
key_mapping.keys.select { |lhs|
lhs.start_with? input
}.tap { |it|
return :matched if it.size == 1 && (it.max_by(&:size)&.size&.== input.size)
return :matching if it.size == 1 && (it.max_by(&:size)&.size&.!= input.size)
return :matched if it.max_by(&:size)&.size&.< input.size
return :matching if it.size > 1
}
key_mapping.keys.select { |lhs|
input.start_with? lhs
}.tap { |it|
return it.size > 0 ? :matched : :unmatched
}
end

def expand(input)
lhs = key_mapping.keys.select { |lhs| input.start_with? lhs }.sort_by(&:size).reverse.first
return input unless lhs
rhs = key_mapping[lhs]

case rhs
when String
rhs_bytes = rhs.bytes
expand(expand(rhs_bytes) + expand(input.drop(lhs.size)))
when Symbol
[rhs] + expand(input.drop(lhs.size))
end
end

def key_mapping
@config[:key_mapping].transform_keys(&:bytes)
end

def clear
@buffer = []
end
end
51 changes: 51 additions & 0 deletions test/key_stroke_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
require 'helper'

class Reline::KeyStroke::Test < Reline::TestCase
using Module.new {
refine Array do
def as_s
map(&:chr).join
end
end
}

def test_input_to!
config = {
key_mapping: {
"a" => "xx",
"ab" => "y",
"abc" => "z",
"x" => "rr"
}
}
stroke = Reline::KeyStroke.new(config)
result = ("abzwabk".bytes).map { |char|
stroke.input_to!(char)&.then { |result|
"#{result.as_s}"
}
}
assert_equal(result, [nil, nil, "yz", "w", nil, nil, "yk"])
end

def test_input_to
config = {
key_mapping: {
"a" => "xx",
"ab" => "y",
"abc" => "z",
"x" => "rr"
}
}
stroke = Reline::KeyStroke.new(config)
assert_equal(stroke.input_to("a".bytes)&.as_s, nil)
assert_equal(stroke.input_to("ab".bytes)&.as_s, nil)
assert_equal(stroke.input_to("abc".bytes)&.as_s, "z")
assert_equal(stroke.input_to("abz".bytes)&.as_s, "yz")
assert_equal(stroke.input_to("abx".bytes)&.as_s, "yrr")
assert_equal(stroke.input_to("ac".bytes)&.as_s, "rrrrc")
assert_equal(stroke.input_to("aa".bytes)&.as_s, "rrrrrrrr")
assert_equal(stroke.input_to("x".bytes)&.as_s, "rr")
assert_equal(stroke.input_to("m".bytes)&.as_s, "m")
assert_equal(stroke.input_to("abzwabk".bytes)&.as_s, "yzwabk")
end
end

0 comments on commit 8bf4989

Please sign in to comment.