This commit is contained in:
aycabta 2019-05-24 23:38:40 +09:00
Родитель 2d34087a38
Коммит eb4e774711
11 изменённых файлов: 282 добавлений и 217 удалений

Просмотреть файл

@ -1,4 +1,5 @@
require 'io/console'
require 'timeout'
require 'reline/version'
require 'reline/config'
require 'reline/key_actor'
@ -6,6 +7,8 @@ require 'reline/key_stroke'
require 'reline/line_editor'
module Reline
Key = Struct.new('Key', :char, :combined_char, :with_meta)
extend self
FILENAME_COMPLETION_PROC = nil
USERNAME_COMPLETION_PROC = nil
@ -321,8 +324,7 @@ module Reline
key_stroke = Reline::KeyStroke.new(config)
begin
loop do
c = Reline::IOGate.getc
key_stroke.input_to!(c)&.then { |inputs|
key_stroke.read_io(@@config.keyseq_timeout) { |inputs|
inputs.each { |c|
@@line_editor.input_key(c)
@@line_editor.rerender

Просмотреть файл

@ -9,7 +9,11 @@ class Reline::ANSI
@@output = val
end
@@buf = []
def self.getc
unless @@buf.empty?
return @@buf.shift
end
c = nil
loop do
result = select([@@input], [], [], 0.1)
@ -20,6 +24,10 @@ class Reline::ANSI
c&.ord
end
def self.ungetc(c)
@@buf.unshift(c)
end
def self.get_screen_size
@@input.winsize
rescue Errno::ENOTTY

Просмотреть файл

@ -16,6 +16,7 @@ class Reline::Config
history-size
horizontal-scroll-mode
input-meta
keyseq-timeout
mark-directories
mark-modified-lines
mark-symlinked-directories
@ -44,6 +45,7 @@ class Reline::Config
@key_actors[:vi_insert] = Reline::KeyActor::ViInsert.new
@key_actors[:vi_command] = Reline::KeyActor::ViCommand.new
@history_size = 500
@keyseq_timeout = 500
end
def reset
@ -178,6 +180,8 @@ class Reline::Config
when 'vi-insert'
@keymap_label = :vi_insert
end
when 'keyseq-timeout'
@keyseq_timeout = value.to_i
end
end

Просмотреть файл

@ -8,6 +8,9 @@ class Reline::GeneralIO
end
def self.getc
unless @@buf.empty?
return @@buf.shift
end
c = nil
loop do
result = select([@@input], [], [], 0.1)
@ -18,6 +21,10 @@ class Reline::GeneralIO
c&.ord
end
def self.ungetc(c)
@@buf.unshift(c)
end
def self.get_screen_size
[1, 1]
end

Просмотреть файл

@ -277,13 +277,13 @@ class Reline::KeyActor::Emacs < Reline::KeyActor::Base
# 137 M-^I
:ed_unassigned,
# 138 M-^J
:ed_unassigned,
:key_newline,
# 139 M-^K
:ed_unassigned,
# 140 M-^L
:ed_clear_screen,
# 141 M-^M
:ed_unassigned,
:key_newline,
# 142 M-^N
:ed_unassigned,
# 143 M-^O

Просмотреть файл

@ -257,261 +257,261 @@ class Reline::KeyActor::ViInsert < Reline::KeyActor::Base
# 127 ^?
:vi_delete_prev_char,
# 128 M-^@
:ed_insert,
:ed_unassigned,
# 129 M-^A
:ed_insert,
:ed_unassigned,
# 130 M-^B
:ed_insert,
:ed_unassigned,
# 131 M-^C
:ed_insert,
:ed_unassigned,
# 132 M-^D
:ed_insert,
:ed_unassigned,
# 133 M-^E
:ed_insert,
:ed_unassigned,
# 134 M-^F
:ed_insert,
:ed_unassigned,
# 135 M-^G
:ed_insert,
:ed_unassigned,
# 136 M-^H
:ed_insert,
:ed_unassigned,
# 137 M-^I
:ed_insert,
:ed_unassigned,
# 138 M-^J
:ed_insert,
:key_newline,
# 139 M-^K
:ed_insert,
:ed_unassigned,
# 140 M-^L
:ed_insert,
:ed_unassigned,
# 141 M-^M
:ed_insert,
:key_newline,
# 142 M-^N
:ed_insert,
:ed_unassigned,
# 143 M-^O
:ed_insert,
:ed_unassigned,
# 144 M-^P
:ed_insert,
:ed_unassigned,
# 145 M-^Q
:ed_insert,
:ed_unassigned,
# 146 M-^R
:ed_insert,
:ed_unassigned,
# 147 M-^S
:ed_insert,
:ed_unassigned,
# 148 M-^T
:ed_insert,
:ed_unassigned,
# 149 M-^U
:ed_insert,
:ed_unassigned,
# 150 M-^V
:ed_insert,
:ed_unassigned,
# 151 M-^W
:ed_insert,
:ed_unassigned,
# 152 M-^X
:ed_insert,
:ed_unassigned,
# 153 M-^Y
:ed_insert,
:ed_unassigned,
# 154 M-^Z
:ed_insert,
:ed_unassigned,
# 155 M-^[
:ed_insert,
:ed_unassigned,
# 156 M-^\
:ed_insert,
:ed_unassigned,
# 157 M-^]
:ed_insert,
:ed_unassigned,
# 158 M-^^
:ed_insert,
:ed_unassigned,
# 159 M-^_
:ed_insert,
:ed_unassigned,
# 160 M-SPACE
:ed_insert,
:ed_unassigned,
# 161 M-!
:ed_insert,
:ed_unassigned,
# 162 M-"
:ed_insert,
:ed_unassigned,
# 163 M-#
:ed_insert,
:ed_unassigned,
# 164 M-$
:ed_insert,
:ed_unassigned,
# 165 M-%
:ed_insert,
:ed_unassigned,
# 166 M-&
:ed_insert,
:ed_unassigned,
# 167 M-'
:ed_insert,
:ed_unassigned,
# 168 M-(
:ed_insert,
:ed_unassigned,
# 169 M-)
:ed_insert,
:ed_unassigned,
# 170 M-*
:ed_insert,
:ed_unassigned,
# 171 M-+
:ed_insert,
:ed_unassigned,
# 172 M-,
:ed_insert,
:ed_unassigned,
# 173 M--
:ed_insert,
:ed_unassigned,
# 174 M-.
:ed_insert,
:ed_unassigned,
# 175 M-/
:ed_insert,
:ed_unassigned,
# 176 M-0
:ed_insert,
:ed_unassigned,
# 177 M-1
:ed_insert,
:ed_unassigned,
# 178 M-2
:ed_insert,
:ed_unassigned,
# 179 M-3
:ed_insert,
:ed_unassigned,
# 180 M-4
:ed_insert,
:ed_unassigned,
# 181 M-5
:ed_insert,
:ed_unassigned,
# 182 M-6
:ed_insert,
:ed_unassigned,
# 183 M-7
:ed_insert,
:ed_unassigned,
# 184 M-8
:ed_insert,
:ed_unassigned,
# 185 M-9
:ed_insert,
:ed_unassigned,
# 186 M-:
:ed_insert,
:ed_unassigned,
# 187 M-;
:ed_insert,
:ed_unassigned,
# 188 M-<
:ed_insert,
:ed_unassigned,
# 189 M-=
:ed_insert,
:ed_unassigned,
# 190 M->
:ed_insert,
:ed_unassigned,
# 191 M-?
:ed_insert,
:ed_unassigned,
# 192 M-@
:ed_insert,
:ed_unassigned,
# 193 M-A
:ed_insert,
:ed_unassigned,
# 194 M-B
:ed_insert,
:ed_unassigned,
# 195 M-C
:ed_insert,
:ed_unassigned,
# 196 M-D
:ed_insert,
:ed_unassigned,
# 197 M-E
:ed_insert,
:ed_unassigned,
# 198 M-F
:ed_insert,
:ed_unassigned,
# 199 M-G
:ed_insert,
:ed_unassigned,
# 200 M-H
:ed_insert,
:ed_unassigned,
# 201 M-I
:ed_insert,
:ed_unassigned,
# 202 M-J
:ed_insert,
:ed_unassigned,
# 203 M-K
:ed_insert,
:ed_unassigned,
# 204 M-L
:ed_insert,
:ed_unassigned,
# 205 M-M
:ed_insert,
:ed_unassigned,
# 206 M-N
:ed_insert,
:ed_unassigned,
# 207 M-O
:ed_insert,
:ed_unassigned,
# 208 M-P
:ed_insert,
:ed_unassigned,
# 209 M-Q
:ed_insert,
:ed_unassigned,
# 210 M-R
:ed_insert,
:ed_unassigned,
# 211 M-S
:ed_insert,
:ed_unassigned,
# 212 M-T
:ed_insert,
:ed_unassigned,
# 213 M-U
:ed_insert,
:ed_unassigned,
# 214 M-V
:ed_insert,
:ed_unassigned,
# 215 M-W
:ed_insert,
:ed_unassigned,
# 216 M-X
:ed_insert,
:ed_unassigned,
# 217 M-Y
:ed_insert,
:ed_unassigned,
# 218 M-Z
:ed_insert,
:ed_unassigned,
# 219 M-[
:ed_insert,
:ed_unassigned,
# 220 M-\
:ed_insert,
:ed_unassigned,
# 221 M-]
:ed_insert,
:ed_unassigned,
# 222 M-^
:ed_insert,
:ed_unassigned,
# 223 M-_
:ed_insert,
:ed_unassigned,
# 223 M-`
:ed_insert,
:ed_unassigned,
# 224 M-a
:ed_insert,
:ed_unassigned,
# 225 M-b
:ed_insert,
:ed_unassigned,
# 226 M-c
:ed_insert,
:ed_unassigned,
# 227 M-d
:ed_insert,
:ed_unassigned,
# 228 M-e
:ed_insert,
:ed_unassigned,
# 229 M-f
:ed_insert,
:ed_unassigned,
# 230 M-g
:ed_insert,
:ed_unassigned,
# 231 M-h
:ed_insert,
:ed_unassigned,
# 232 M-i
:ed_insert,
:ed_unassigned,
# 233 M-j
:ed_insert,
:ed_unassigned,
# 234 M-k
:ed_insert,
:ed_unassigned,
# 235 M-l
:ed_insert,
:ed_unassigned,
# 236 M-m
:ed_insert,
:ed_unassigned,
# 237 M-n
:ed_insert,
:ed_unassigned,
# 238 M-o
:ed_insert,
:ed_unassigned,
# 239 M-p
:ed_insert,
:ed_unassigned,
# 240 M-q
:ed_insert,
:ed_unassigned,
# 241 M-r
:ed_insert,
:ed_unassigned,
# 242 M-s
:ed_insert,
:ed_unassigned,
# 243 M-t
:ed_insert,
:ed_unassigned,
# 244 M-u
:ed_insert,
:ed_unassigned,
# 245 M-v
:ed_insert,
:ed_unassigned,
# 246 M-w
:ed_insert,
:ed_unassigned,
# 247 M-x
:ed_insert,
:ed_unassigned,
# 248 M-y
:ed_insert,
:ed_unassigned,
# 249 M-z
:ed_insert,
:ed_unassigned,
# 250 M-{
:ed_insert,
:ed_unassigned,
# 251 M-|
:ed_insert,
:ed_unassigned,
# 252 M-}
:ed_insert,
:ed_unassigned,
# 253 M-~
:ed_insert,
:ed_unassigned,
# 254 M-^?
:ed_insert
:ed_unassigned
# 255
# EOF
]

Просмотреть файл

@ -13,30 +13,81 @@ class Reline::KeyStroke
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
# Keystrokes of GNU Readline will timeout it with the specification of
# "keyseq-timeout" when waiting for the 2nd character after the 1st one.
# If the 2nd character comes after 1st ESC without timeout it has a
# meta-property of meta-key to discriminate modified key with meta-key
# from multibyte characters that come with 8th bit on.
#
# GNU Readline will wait for the 2nd character with "keyseq-timeout"
# milli-seconds but wait forever after 3rd characters.
def read_io(keyseq_timeout, &block)
buffer = []
loop do
c = Reline::IOGate.getc
buffer << c
result = match_status(buffer)
case result
when :matched
block.(expand(bytes).map{ |c| Reline::Key.new(c, c, false) })
break
when :matching
if buffer.size == 1
begin
succ_c = nil
Timeout.timeout(keyseq_timeout / 1000.0) {
succ_c = Reline::IOGate.getc
}
rescue Timeout::Error # cancel matching only when first byte
block.([Reline::Key.new(c, c, false)])
break
else
if match_status(buffer.dup.push(succ_c)) == :unmatched
if c == "\e".ord
block.([Reline::Key.new(succ_c, succ_c | 0b10000000, true)])
else
block.([Reline::Key.new(c, c, false), Reline::Key.new(succ_c, succ_c, false)])
end
break
else
Reline::IOGate.ungetc(succ_c)
end
end
end
when :unmatched
if buffer.size == 1 and c == "\e".ord
read_escaped_key(buffer, block)
else
block.(buffer.map{ |c| Reline::Key.new(c, c, false) })
end
break
end
end
end
def input_to!(bytes)
if bytes.nil?
return @buffer.push(nil)&.tap { clear }
def read_escaped_key(buffer, block)
begin
escaped_c = nil
Timeout.timeout(keyseq_timeout / 1000.0) {
escaped_c = Reline::IOGate.getc
}
rescue Timeout::Error # independent ESC
block.([Reline::Key.new(c, c, false)])
else
if escaped_c.nil?
block.([Reline::Key.new(c, c, false)])
elsif escaped_c >= 128 # maybe, first byte of multi byte
block.([Reline::Key.new(c, c, false), Reline::Key.new(escaped_c, escaped_c, false)])
elsif escaped_c == "\e".ord # escape twice
block.([Reline::Key.new(c, c, false), Reline::Key.new(c, c, false)])
else
block.([Reline::Key.new(escaped_c, escaped_c | 0b10000000, true)])
end
end
@buffer.concat Array(bytes)
input_to(@buffer)&.tap { clear }
end
private
def match_status(input)
key_mapping.keys.select { |lhs|
lhs.start_with? input
@ -53,6 +104,8 @@ class Reline::KeyStroke
}
end
private
def expand(input)
lhs = key_mapping.keys.select { |lhs| input.start_with? lhs }.sort_by(&:size).reverse.first
return input unless lhs
@ -70,8 +123,4 @@ class Reline::KeyStroke
def key_mapping
@config[:key_mapping].transform_keys(&:bytes)
end
def clear
@buffer = []
end
end

Просмотреть файл

@ -104,7 +104,6 @@ class Reline::LineEditor
@kill_ring = Reline::KillRing.new
@vi_clipboard = ''
@vi_arg = nil
@meta_prefix = false
@waiting_proc = nil
@waiting_operator_proc = nil
@completion_journey_data = nil
@ -625,31 +624,38 @@ class Reline::LineEditor
private def normal_char(key)
method_symbol = method_obj = nil
@multibyte_buffer << key
@multibyte_buffer << key.combined_char
if @multibyte_buffer.size > 1
if @multibyte_buffer.dup.force_encoding(@encoding).valid_encoding?
key = @multibyte_buffer.dup.force_encoding(@encoding)
process_key(@multibyte_buffer.dup.force_encoding(@encoding), nil, nil)
@multibyte_buffer.clear
else
# invalid
return
end
else # single byte
return if key >= 128 # maybe, first byte of multi byte
if @meta_prefix
key |= 0b10000000 if key.nobits?(0b10000000)
@meta_prefix = false
end
method_symbol = @config.editing_mode.get_method(key)
if key.allbits?(0b10000000) and method_symbol == :ed_unassigned
return # This is unknown input
end
if method_symbol and respond_to?(method_symbol, true)
method_obj = method(method_symbol)
return if key.char >= 128 # maybe, first byte of multi byte
method_symbol = @config.editing_mode.get_method(key.combined_char)
if key.with_meta and method_symbol == :ed_unassigned
# split ESC + key
method_symbol = @config.editing_mode.get_method("\e".ord)
if method_symbol and respond_to?(method_symbol, true)
method_obj = method(method_symbol)
end
process_key("\e".ord, method_symbol, method_obj)
method_symbol = @config.editing_mode.get_method(key.char)
if method_symbol and respond_to?(method_symbol, true)
method_obj = method(method_symbol)
end
process_key(key.char, method_symbol, method_obj)
else
if method_symbol and respond_to?(method_symbol, true)
method_obj = method(method_symbol)
end
process_key(key.combined_char, method_symbol, method_obj)
end
@multibyte_buffer.clear
end
process_key(key, method_symbol, method_obj)
if @config.editing_mode_is?(:vi_command) and @cursor > 0 and @cursor == @cursor_max
byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
@byte_pointer -= byte_size
@ -660,7 +666,7 @@ class Reline::LineEditor
end
def input_key(key)
if key.nil?
if key.nil? or key.char.nil?
if @first_char
@line = nil
end
@ -669,30 +675,20 @@ class Reline::LineEditor
end
@first_char = false
completion_occurs = false
if @config.editing_mode_is?(:emacs, :vi_insert) and key == "\C-i".ord
if @config.editing_mode_is?(:emacs, :vi_insert) and key.char == "\C-i".ord
result = @completion_proc&.(@line)
if result.is_a?(Array)
completion_occurs = true
complete(result)
end
elsif @config.editing_mode_is?(:vi_insert) and ["\C-p".ord, "\C-n".ord].include?(key)
elsif @config.editing_mode_is?(:vi_insert) and ["\C-p".ord, "\C-n".ord].include?(key.char)
result = @completion_proc&.(@line)
if result.is_a?(Array)
completion_occurs = true
move_completed_list(result, "\C-p".ord == key ? :up : :down)
move_completed_list(result, "\C-p".ord == key.char ? :up : :down)
end
elsif @config.editing_mode_is?(:emacs) and key == "\e".ord # meta key
if @meta_prefix
# escape twice
@meta_prefix = false
@kill_ring.process
else
@meta_prefix = true
end
elsif @config.editing_mode_is?(:vi_command) and key == "\e".ord
# suppress ^[ when command_mode
elsif Symbol === key and respond_to?(key, true)
process_key(key, key, method(key))
elsif Symbol === key.char and respond_to?(key.char, true)
process_key(key.char, key.char, method(key.char))
else
normal_char(key)
end
@ -823,6 +819,15 @@ class Reline::LineEditor
end
end
private def key_newline(key)
if @is_multiline
next_line = @line.byteslice(@byte_pointer, @line.bytesize - @byte_pointer)
cursor_line = @line.byteslice(0, @byte_pointer)
insert_new_line(cursor_line, next_line)
@cursor = 0
end
end
private def ed_insert(key)
if key.instance_of?(String)
width = Reline::Unicode.get_mbchar_width(key)

Просмотреть файл

@ -101,6 +101,10 @@ class Reline::Windows
end
end
def self.ungetc(c)
@@buf.unshift(c)
end
def self.get_screen_size
csbi = 0.chr * 24
@@GetConsoleScreenBufferInfo.call(@@hConsoleHandle, csbi)

Просмотреть файл

@ -40,13 +40,13 @@ class Reline::TestCase < Test::Unit::TestCase
eighth_bit = 0b10000000
byte = c.bytes.first
if byte.allbits?(eighth_bit)
@line_editor.input_key("\e".ord)
byte ^= eighth_bit
@line_editor.input_key(Reline::Key.new(byte ^ eighth_bit, byte, true))
else
@line_editor.input_key(Reline::Key.new(byte, byte, false))
end
@line_editor.input_key(byte)
else
c.bytes.each do |b|
@line_editor.input_key(b)
@line_editor.input_key(Reline::Key.new(b, b, false))
end
end
end

Просмотреть файл

@ -4,12 +4,16 @@ class Reline::KeyStroke::Test < Reline::TestCase
using Module.new {
refine Array do
def as_s
map(&:chr).join
join
end
def to_keys
map{ |b| Reline::Key.new(b, b, false) }
end
end
}
def test_input_to!
def test_match_status
config = {
key_mapping: {
"a" => "xx",
@ -19,33 +23,15 @@ class Reline::KeyStroke::Test < Reline::TestCase
}
}
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")
assert_equal(:matching, stroke.match_status("a".bytes))
assert_equal(:matching, stroke.match_status("ab".bytes))
assert_equal(:matched, stroke.match_status("abc".bytes))
assert_equal(:matched, stroke.match_status("abz".bytes))
assert_equal(:matched, stroke.match_status("abx".bytes))
assert_equal(:matched, stroke.match_status("ac".bytes))
assert_equal(:matched, stroke.match_status("aa".bytes))
assert_equal(:matched, stroke.match_status("x".bytes))
assert_equal(:unmatched, stroke.match_status("m".bytes))
assert_equal(:matched, stroke.match_status("abzwabk".bytes))
end
end