1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00

Add Reline as a fallback library for Readline

* lib/reine.rb, lib/reline/*: Reline is a readline stdlib compatible
  library.
* lib/readline.rb: Readline uses a fallback to Reline when ext/readline
  doesn't exist.
* tool/sync_default_gems.rb: add ruby/reline as a default gem.
* appveyor.yml: add "set RELINE_TEST_ENCODING=Windows-31J" for test suit
  of Reline, and add "--exclude readline" to "nmake test-all" on Visual
  Studio builds because of strange behavior.
* spec/ruby/library/readline/spec_helper.rb: skip Reline as with
  RbReadline.
This commit is contained in:
aycabta 2019-04-27 14:53:09 +09:00
parent eb45ba6116
commit 17350c7e55
26 changed files with 8103 additions and 3 deletions

View file

@ -63,9 +63,10 @@ for:
- if not "%GEMS_FOR_TEST%" == "" \usr\bin\gem install --no-document %GEMS_FOR_TEST%
test_script:
- set /a JOBS=%NUMBER_OF_PROCESSORS%
- set RELINE_TEST_ENCODING=Windows-31J
- nmake -l "TESTOPTS=-v -q" btest
- nmake -l "TESTOPTS=-v -q" test-basic
- nmake -l "TESTOPTS=-q --subprocess-timeout-scale=3.0 --excludes=../test/excludes/_appveyor -j%JOBS% --exclude win32ole --exclude test_bignum --exclude test_syntax --exclude test_open-uri --exclude test_bundled_ca --exclude test_gc_compact" test-all
- nmake -l "TESTOPTS=-q --subprocess-timeout-scale=3.0 --excludes=../test/excludes/_appveyor -j%JOBS% --exclude readline --exclude win32ole --exclude test_bignum --exclude test_syntax --exclude test_open-uri --exclude test_bundled_ca --exclude test_gc_compact" test-all
# separately execute tests without -j which may crash worker with -j.
- nmake -l "TESTOPTS=-v --subprocess-timeout-scale=3.0 --excludes=../test/excludes/_appveyor" test-all TESTS="../test/win32ole ../test/ruby/test_bignum.rb ../test/ruby/test_syntax.rb ../test/open-uri/test_open-uri.rb ../test/rubygems/test_bundled_ca.rb ../test/ruby/test_gc_compact.rb"
- nmake -l test-spec MSPECOPT=-fs # not using `-j` because sometimes `mspec -j` silently dies on Windows
@ -108,6 +109,7 @@ for:
- mingw32-make DESTDIR=../install install-nodoc
- if not "%GEMS_FOR_TEST%" == "" ..\install\bin\gem install --no-document %GEMS_FOR_TEST%
test_script:
- set RELINE_TEST_ENCODING=Windows-31J
- mingw32-make test
- mingw32-make test-all TESTOPTS="--retry --job-status=normal --show-skip --subprocess-timeout-scale=1.5 --excludes=../ruby/test/excludes/_appveyor -j %JOBS% --exclude win32ole --exclude test_open-uri --exclude test_gc_compact"
# separately execute tests without -j which may crash worker with -j.

6
lib/readline.rb Normal file
View file

@ -0,0 +1,6 @@
begin
require 'readline.so'
rescue LoadError
require 'reline'
Readline = Reline
end

197
lib/reline.rb Normal file
View file

@ -0,0 +1,197 @@
require 'io/console'
require 'reline/version'
require 'reline/config'
require 'reline/key_actor'
require 'reline/key_stroke'
require 'reline/line_editor'
module Reline
extend self
FILENAME_COMPLETION_PROC = nil
USERNAME_COMPLETION_PROC = nil
HISTORY = Array.new
if RUBY_PLATFORM =~ /mswin|mingw/
IS_WINDOWS = true
else
IS_WINDOWS = false
end
CursorPos = Struct.new(:x, :y)
class << self
attr_accessor :basic_quote_characters
attr_accessor :completer_quote_characters
attr_accessor :completer_word_break_characters
attr_reader :completion_append_character
attr_accessor :completion_case_fold
attr_accessor :filename_quote_characters
attr_writer :input
attr_writer :output
end
@@ambiguous_width = nil
@@config = nil
@basic_quote_characters = '"\''
# TODO implement below
#@completer_quote_characters
#@completion_append_character
#@completion_case_fold
#@filename_quote_characters
def self.completion_append_character=(val)
if val.nil?
@completion_append_character = nil
elsif val.size == 1
@completion_append_character = val
elsif val.size > 1
@completion_append_character = val[0]
else
@completion_append_character = val
end
end
@@basic_word_break_characters = " \t\n`><=;|&{("
def self.basic_word_break_characters
@@basic_word_break_characters
end
def self.basic_word_break_characters=(v)
@@basic_word_break_characters = v
end
@@completer_word_break_characters = @@basic_word_break_characters.dup
@@completion_proc = nil
def self.completion_proc
@@completion_proc
end
def self.completion_proc=(p)
@@completion_proc = p
end
@@dig_perfect_match_proc = nil
def self.dig_perfect_match_proc
@@dig_perfect_match_proc
end
def self.dig_perfect_match_proc=(p)
@@dig_perfect_match_proc = p
end
if IS_WINDOWS
require 'reline/windows'
else
require 'reline/ansi'
end
def retrieve_completion_block(line, byte_pointer)
break_regexp = /[#{Regexp.escape(@@basic_word_break_characters)}]/
before_pointer = line.byteslice(0, byte_pointer)
break_point = before_pointer.rindex(break_regexp)
if break_point
preposing = before_pointer[0..(break_point)]
block = before_pointer[(break_point + 1)..-1]
else
preposing = ''
block = before_pointer
end
postposing = line.byteslice(byte_pointer, line.bytesize)
[preposing, block, postposing]
end
def readmultiline(prompt = '', add_hist = false, &confirm_multiline_termination)
if block_given?
inner_readline(prompt, add_hist, true, &confirm_multiline_termination)
else
inner_readline(prompt, add_hist, true)
end
if add_hist and @line_editor.whole_buffer and @line_editor.whole_buffer.chomp.size > 0
Reline::HISTORY << @line_editor.whole_buffer
end
@line_editor.whole_buffer
end
def readline(prompt = '', add_hist = false)
inner_readline(prompt, add_hist, false)
if add_hist and @line_editor.line and @line_editor.line.chomp.size > 0
Reline::HISTORY << @line_editor.line.chomp
end
@line_editor.line
end
def inner_readline(prompt, add_hist, multiline, &confirm_multiline_termination)
if @@config.nil?
@@config = Reline::Config.new
@@config.read
end
otio = prep
may_req_ambiguous_char_width
@line_editor = Reline::LineEditor.new(@@config, prompt)
if multiline
@line_editor.multiline_on
if block_given?
@line_editor.confirm_multiline_termination_proc = confirm_multiline_termination
end
end
@line_editor.completion_proc = @@completion_proc
@line_editor.dig_perfect_match_proc = @@dig_perfect_match_proc
@line_editor.retrieve_completion_block = method(:retrieve_completion_block)
@line_editor.rerender
if IS_WINDOWS
config = {
key_mapping: {
[224, 72] => :ed_prev_history, # ↑
[224, 80] => :ed_next_history, # ↓
[224, 77] => :ed_next_char, # →
[224, 75] => :ed_prev_char # ←
}
}
else
config = {
key_mapping: {
[27, 91, 65] => :ed_prev_history, # ↑
[27, 91, 66] => :ed_next_history, # ↓
[27, 91, 67] => :ed_next_char, # →
[27, 91, 68] => :ed_prev_char # ←
}
}
end
key_stroke = Reline::KeyStroke.new(config)
begin
while c = getc
key_stroke.input_to!(c)&.then { |inputs|
inputs.each { |c|
@line_editor.input_key(c)
@line_editor.rerender
}
}
break if @line_editor.finished?
end
Reline.move_cursor_column(0)
rescue StandardError => e
deprep(otio)
raise e
end
deprep(otio)
end
def may_req_ambiguous_char_width
return if @@ambiguous_width
Reline.move_cursor_column(0)
print "\u{25bd}"
@@ambiguous_width = Reline.cursor_pos.x
Reline.move_cursor_column(0)
Reline.erase_after_cursor
end
def self.ambiguous_width
@@ambiguous_width
end
end

87
lib/reline/ansi.rb Normal file
View file

@ -0,0 +1,87 @@
module Reline
def getc
c = nil
until c
return nil if @line_editor.finished?
result = select([$stdin], [], [], 0.1)
next if result.nil?
c = $stdin.read(1)
end
c.ord
end
def self.get_screen_size
$stdin.winsize
end
def self.set_screen_size(rows, columns)
$stdin.winsize = [rows, columns]
self
end
def self.cursor_pos
res = ''
$stdin.raw do |stdin|
$stdout << "\e[6n"
$stdout.flush
while (c = stdin.getc) != 'R'
res << c if c
end
end
m = res.match(/(?<row>\d+);(?<column>\d+)/)
CursorPos.new(m[:column].to_i - 1, m[:row].to_i - 1)
end
def self.move_cursor_column(x)
print "\e[#{x + 1}G"
end
def self.move_cursor_up(x)
if x > 0
print "\e[#{x}A" if x > 0
elsif x < 0
move_cursor_down(-x)
end
end
def self.move_cursor_down(x)
if x > 0
print "\e[#{x}B" if x > 0
elsif x < 0
move_cursor_up(-x)
end
end
def self.erase_after_cursor
print "\e[K"
end
def self.scroll_down(x)
return if x.zero?
print "\e[#{x}S"
end
def self.clear_screen
print "\e[2J"
print "\e[1;1H"
end
def prep
int_handle = Signal.trap('INT', 'IGNORE')
otio = `stty -g`.chomp
setting = ' -echo -icrnl cbreak'
if (`stty -a`.scan(/-parenb\b/).first == '-parenb')
setting << ' pass8'
end
setting << ' -ixoff'
`stty #{setting}`
Signal.trap('INT', int_handle)
otio
end
def deprep(otio)
int_handle = Signal.trap('INT', 'IGNORE')
`stty #{otio}`
Signal.trap('INT', int_handle)
end
end

235
lib/reline/config.rb Normal file
View file

@ -0,0 +1,235 @@
require 'pathname'
class Reline::Config
DEFAULT_PATH = Pathname.new(Dir.home).join('.inputrc')
def initialize
@skip_section = nil
@if_stack = []
@editing_mode_label = :emacs
@keymap_label = :emacs
@key_actors = {}
@key_actors[:emacs] = Reline::KeyActor::Emacs.new
@key_actors[:vi_insert] = Reline::KeyActor::ViInsert.new
@key_actors[:vi_command] = Reline::KeyActor::ViCommand.new
end
def reset
if editing_mode_is?(:vi_command)
@editing_mode_label = :vi_insert
end
end
def editing_mode
@key_actors[@editing_mode_label]
end
def editing_mode=(val)
@editing_mode_label = val
end
def editing_mode_is?(*val)
(val.respond_to?(:any?) ? val : [val]).any?(@editing_mode_label)
end
def keymap
@key_actors[@keymap_label]
end
def read(file = DEFAULT_PATH)
begin
if file.respond_to?(:readlines)
lines = file.readlines
else
File.open(file, 'rt') do |f|
lines = f.readlines
end
end
rescue Errno::ENOENT
$stderr.puts "no such file #{file}"
return nil
end
read_lines(lines)
self
end
def read_lines(lines)
lines.each do |line|
line = line.chomp.gsub(/^\s*/, '')
if line[0, 1] == '$'
handle_directive(line[1..-1])
next
end
next if @skip_section
if line.match(/^set +([^ ]+) +([^ ]+)/i)
var, value = $1.downcase, $2.downcase
bind_variable(var, value)
next
end
if line =~ /\s*(.*)\s*:\s*(.*)\s*$/
key, func_name = $1, $2
bind_key(key, func_name)
end
end
end
def handle_directive(directive)
directive, args = directive.split(' ')
case directive
when 'if'
condition = false
case args # TODO: variables
when 'mode'
when 'term'
when 'version'
else # application name
condition = true if args == 'Ruby'
end
unless @skip_section.nil?
@if_stack << @skip_section
end
@skip_section = !condition
when 'else'
@skip_section = !@skip_section
when 'endif'
@skip_section = nil
unless @if_stack.empty?
@skip_section = @if_stack.pop
end
when 'include'
read(args)
end
end
def bind_variable(name, value)
case name
when %w{
bind-tty-special-chars
blink-matching-paren
byte-oriented
completion-ignore-case
convert-meta
disable-completion
enable-keypad
expand-tilde
history-preserve-point
horizontal-scroll-mode
input-meta
mark-directories
mark-modified-lines
mark-symlinked-directories
match-hidden-files
meta-flag
output-meta
page-completions
prefer-visible-bell
print-completions-horizontally
show-all-if-ambiguous
show-all-if-unmodified
visible-stats
} then
variable_name = :"@#{name.tr(?-, ?_)}"
instance_variable_set(variable_name, value.nil? || value == '1' || value == 'on')
when 'bell-style'
@bell_style =
case value
when 'none', 'off'
:none
when 'audible', 'on'
:audible
when 'visible'
:visible
else
:audible
end
when 'comment-begin'
@comment_begin = value.dup
when 'completion-query-items'
@completion_query_items = value.to_i
when 'isearch-terminators'
@isearch_terminators = instance_eval(value)
when 'editing-mode'
case value
when 'emacs'
@editing_mode_label = :emacs
@keymap_label = :emacs
when 'vi'
@editing_mode_label = :vi_insert
@keymap_label = :vi_insert
end
when 'keymap'
case value
when 'emacs', 'emacs-standard', 'emacs-meta', 'emacs-ctlx'
@keymap_label = :emacs
when 'vi', 'vi-move', 'vi-command'
@keymap_label = :vi_command
when 'vi-insert'
@keymap_label = :vi_insert
end
end
end
def bind_key(key, func_name)
if key =~ /"(.*)"/
keyseq = parse_keyseq($1).force_encoding('ASCII-8BIT')
else
keyseq = nil
end
if func_name =~ /"(.*)"/
func = parse_keyseq($1).force_encoding('ASCII-8BIT')
else
func = func_name.to_sym # It must be macro.
end
[keyseq, func]
end
def key_notation_to_char(notation)
case notation
when /\\C-([A-Za-z_])/
(1 + $1.downcase.ord - ?a.ord).chr('ASCII-8BIT')
when /\\M-([0-9A-Za-z_])/
modified_key = $1
code =
case $1
when /[0-9]/
?\M-0.bytes.first + (modified_key.ord - ?0.ord)
when /[A-Z]/
?\M-A.bytes.first + (modified_key.ord - ?A.ord)
when /[a-z]/
?\M-a.bytes.first + (modified_key.ord - ?a.ord)
end
code.chr('ASCII-8BIT')
when /\\C-M-[A-Za-z_]/, /\\M-C-[A-Za-z_]/
# 129 M-^A
when /\\(\d{1,3})/ then $1.to_i(8).chr # octal
when /\\x(\h{1,2})/ then $1.to_i(16).chr # hexadecimal
when "\\e" then ?\e
when "\\\\" then ?\\
when "\\\"" then ?"
when "\\'" then ?'
when "\\a" then ?\a
when "\\b" then ?\b
when "\\d" then ?\d
when "\\f" then ?\f
when "\\n" then ?\n
when "\\r" then ?\r
when "\\t" then ?\t
when "\\v" then ?\v
else notation
end
end
def parse_keyseq(str)
# TODO: Control- and Meta-
ret = String.new(encoding: 'ASCII-8BIT')
while str =~ /(\\C-[A-Za-z_]|\\M-[0-9A-Za-z_]|\\C-M-[A-Za-z_]|\\M-C-[A-Za-z_]|\\e|\\\\|\\"|\\'|\\a|\\b|\\d|\\f|\\n|\\r|\\t|\\v|\\\d{1,3}|\\x\h{1,2}|.)/
ret << key_notation_to_char($&)
str = $'
end
ret
end
end

7
lib/reline/key_actor.rb Normal file
View file

@ -0,0 +1,7 @@
module Reline::KeyActor
end
require 'reline/key_actor/base'
require 'reline/key_actor/emacs'
require 'reline/key_actor/vi_command'
require 'reline/key_actor/vi_insert'

View file

@ -0,0 +1,7 @@
class Reline::KeyActor::Base
MAPPING = Array.new(256)
def get_method(key)
self.class::MAPPING[key]
end
end

View file

@ -0,0 +1,518 @@
class Reline::KeyActor::Emacs < Reline::KeyActor::Base
MAPPING = [
# 0 ^@
:em_set_mark,
# 1 ^A
:ed_move_to_beg,
# 2 ^B
:ed_prev_char,
# 3 ^C
:ed_ignore,
# 4 ^D
:em_delete_or_list,
# 5 ^E
:ed_move_to_end,
# 6 ^F
:ed_next_char,
# 7 ^G
:ed_unassigned,
# 8 ^H
:em_delete_prev_char,
# 9 ^I
:ed_unassigned,
# 10 ^J
:ed_newline,
# 11 ^K
:ed_kill_line,
# 12 ^L
:ed_clear_screen,
# 13 ^M
:ed_newline,
# 14 ^N
:ed_next_history,
# 15 ^O
:ed_ignore,
# 16 ^P
:ed_prev_history,
# 17 ^Q
:ed_ignore,
# 18 ^R
:ed_redisplay,
# 19 ^S
:ed_ignore,
# 20 ^T
:ed_transpose_chars,
# 21 ^U
:em_kill_line,
# 22 ^V
:ed_quoted_insert,
# 23 ^W
:em_kill_region,
# 24 ^X
:ed_sequence_lead_in,
# 25 ^Y
:em_yank,
# 26 ^Z
:ed_ignore,
# 27 ^[
:em_meta_next,
# 28 ^\
:ed_ignore,
# 29 ^]
:ed_ignore,
# 30 ^^
:ed_unassigned,
# 31 ^_
:ed_unassigned,
# 32 SPACE
:ed_insert,
# 33 !
:ed_insert,
# 34 "
:ed_insert,
# 35 #
:ed_insert,
# 36 $
:ed_insert,
# 37 %
:ed_insert,
# 38 &
:ed_insert,
# 39 '
:ed_insert,
# 40 (
:ed_insert,
# 41 )
:ed_insert,
# 42 *
:ed_insert,
# 43 +
:ed_insert,
# 44 ,
:ed_insert,
# 45 -
:ed_insert,
# 46 .
:ed_insert,
# 47 /
:ed_insert,
# 48 0
:ed_digit,
# 49 1
:ed_digit,
# 50 2
:ed_digit,
# 51 3
:ed_digit,
# 52 4
:ed_digit,
# 53 5
:ed_digit,
# 54 6
:ed_digit,
# 55 7
:ed_digit,
# 56 8
:ed_digit,
# 57 9
:ed_digit,
# 58 :
:ed_insert,
# 59 ;
:ed_insert,
# 60 <
:ed_insert,
# 61 =
:ed_insert,
# 62 >
:ed_insert,
# 63 ?
:ed_insert,
# 64 @
:ed_insert,
# 65 A
:ed_insert,
# 66 B
:ed_insert,
# 67 C
:ed_insert,
# 68 D
:ed_insert,
# 69 E
:ed_insert,
# 70 F
:ed_insert,
# 71 G
:ed_insert,
# 72 H
:ed_insert,
# 73 I
:ed_insert,
# 74 J
:ed_insert,
# 75 K
:ed_insert,
# 76 L
:ed_insert,
# 77 M
:ed_insert,
# 78 N
:ed_insert,
# 79 O
:ed_insert,
# 80 P
:ed_insert,
# 81 Q
:ed_insert,
# 82 R
:ed_insert,
# 83 S
:ed_insert,
# 84 T
:ed_insert,
# 85 U
:ed_insert,
# 86 V
:ed_insert,
# 87 W
:ed_insert,
# 88 X
:ed_insert,
# 89 Y
:ed_insert,
# 90 Z
:ed_insert,
# 91 [
:ed_insert,
# 92 \
:ed_insert,
# 93 ]
:ed_insert,
# 94 ^
:ed_insert,
# 95 _
:ed_insert,
# 96 `
:ed_insert,
# 97 a
:ed_insert,
# 98 b
:ed_insert,
# 99 c
:ed_insert,
# 100 d
:ed_insert,
# 101 e
:ed_insert,
# 102 f
:ed_insert,
# 103 g
:ed_insert,
# 104 h
:ed_insert,
# 105 i
:ed_insert,
# 106 j
:ed_insert,
# 107 k
:ed_insert,
# 108 l
:ed_insert,
# 109 m
:ed_insert,
# 110 n
:ed_insert,
# 111 o
:ed_insert,
# 112 p
:ed_insert,
# 113 q
:ed_insert,
# 114 r
:ed_insert,
# 115 s
:ed_insert,
# 116 t
:ed_insert,
# 117 u
:ed_insert,
# 118 v
:ed_insert,
# 119 w
:ed_insert,
# 120 x
:ed_insert,
# 121 y
:ed_insert,
# 122 z
:ed_insert,
# 123 {
:ed_insert,
# 124 |
:ed_insert,
# 125 }
:ed_insert,
# 126 ~
:ed_insert,
# 127 ^?
:em_delete_prev_char,
# 128 M-^@
:ed_unassigned,
# 129 M-^A
:ed_unassigned,
# 130 M-^B
:ed_unassigned,
# 131 M-^C
:ed_unassigned,
# 132 M-^D
:ed_unassigned,
# 133 M-^E
:ed_unassigned,
# 134 M-^F
:ed_unassigned,
# 135 M-^G
:ed_unassigned,
# 136 M-^H
:ed_delete_prev_word,
# 137 M-^I
:ed_unassigned,
# 138 M-^J
:ed_unassigned,
# 139 M-^K
:ed_unassigned,
# 140 M-^L
:ed_clear_screen,
# 141 M-^M
:ed_unassigned,
# 142 M-^N
:ed_unassigned,
# 143 M-^O
:ed_unassigned,
# 144 M-^P
:ed_unassigned,
# 145 M-^Q
:ed_unassigned,
# 146 M-^R
:ed_unassigned,
# 147 M-^S
:ed_unassigned,
# 148 M-^T
:ed_unassigned,
# 149 M-^U
:ed_unassigned,
# 150 M-^V
:ed_unassigned,
# 151 M-^W
:ed_unassigned,
# 152 M-^X
:ed_unassigned,
# 153 M-^Y
:ed_unassigned,
# 154 M-^Z
:ed_unassigned,
# 155 M-^[
:ed_unassigned,
# 156 M-^\
:ed_unassigned,
# 157 M-^]
:ed_unassigned,
# 158 M-^^
:ed_unassigned,
# 159 M-^_
:em_copy_prev_word,
# 160 M-SPACE
:ed_unassigned,
# 161 M-!
:ed_unassigned,
# 162 M-"
:ed_unassigned,
# 163 M-#
:ed_unassigned,
# 164 M-$
:ed_unassigned,
# 165 M-%
:ed_unassigned,
# 166 M-&
:ed_unassigned,
# 167 M-'
:ed_unassigned,
# 168 M-(
:ed_unassigned,
# 169 M-)
:ed_unassigned,
# 170 M-*
:ed_unassigned,
# 171 M-+
:ed_unassigned,
# 172 M-,
:ed_unassigned,
# 173 M--
:ed_unassigned,
# 174 M-.
:ed_unassigned,
# 175 M-/
:ed_unassigned,
# 176 M-0
:ed_argument_digit,
# 177 M-1
:ed_argument_digit,
# 178 M-2
:ed_argument_digit,
# 179 M-3
:ed_argument_digit,
# 180 M-4
:ed_argument_digit,
# 181 M-5
:ed_argument_digit,
# 182 M-6
:ed_argument_digit,
# 183 M-7
:ed_argument_digit,
# 184 M-8
:ed_argument_digit,
# 185 M-9
:ed_argument_digit,
# 186 M-:
:ed_unassigned,
# 187 M-;
:ed_unassigned,
# 188 M-<
:ed_unassigned,
# 189 M-=
:ed_unassigned,
# 190 M->
:ed_unassigned,
# 191 M-?
:ed_unassigned,
# 192 M-@
:ed_unassigned,
# 193 M-A
:ed_unassigned,
# 194 M-B
:ed_prev_word,
# 195 M-C
:em_capitol_case,
# 196 M-D
:em_delete_next_word,
# 197 M-E
:ed_unassigned,
# 198 M-F
:em_next_word,
# 199 M-G
:ed_unassigned,
# 200 M-H
:ed_unassigned,
# 201 M-I
:ed_unassigned,
# 202 M-J
:ed_unassigned,
# 203 M-K
:ed_unassigned,
# 204 M-L
:em_lower_case,
# 205 M-M
:ed_unassigned,
# 206 M-N
:ed_search_next_history,
# 207 M-O
:ed_sequence_lead_in,
# 208 M-P
:ed_search_prev_history,
# 209 M-Q
:ed_unassigned,
# 210 M-R
:ed_unassigned,
# 211 M-S
:ed_unassigned,
# 212 M-T
:ed_unassigned,
# 213 M-U
:em_upper_case,
# 214 M-V
:ed_unassigned,
# 215 M-W
:em_copy_region,
# 216 M-X
:ed_command,
# 217 M-Y
:ed_unassigned,
# 218 M-Z
:ed_unassigned,
# 219 M-[
:ed_sequence_lead_in,
# 220 M-\
:ed_unassigned,
# 221 M-]
:ed_unassigned,
# 222 M-^
:ed_unassigned,
# 223 M-_
:ed_unassigned,
# 223 M-`
:ed_unassigned,
# 224 M-a
:ed_unassigned,
# 225 M-b
:ed_prev_word,
# 226 M-c
:em_capitol_case,
# 227 M-d
:em_delete_next_word,
# 228 M-e
:ed_unassigned,
# 229 M-f
:em_next_word,
# 230 M-g
:ed_unassigned,
# 231 M-h
:ed_unassigned,
# 232 M-i
:ed_unassigned,
# 233 M-j
:ed_unassigned,
# 234 M-k
:ed_unassigned,
# 235 M-l
:em_lower_case,
# 236 M-m
:ed_unassigned,
# 237 M-n
:ed_search_next_history,
# 238 M-o
:ed_unassigned,
# 239 M-p
:ed_search_prev_history,
# 240 M-q
:ed_unassigned,
# 241 M-r
:ed_unassigned,
# 242 M-s
:ed_unassigned,
# 243 M-t
:ed_unassigned,
# 244 M-u
:em_upper_case,
# 245 M-v
:ed_unassigned,
# 246 M-w
:em_copy_region,
# 247 M-x
:ed_command,
# 248 M-y
:ed_unassigned,
# 249 M-z
:ed_unassigned,
# 250 M-{
:ed_unassigned,
# 251 M-|
:ed_unassigned,
# 252 M-}
:ed_unassigned,
# 253 M-~
:ed_unassigned,
# 254 M-^?
:ed_delete_prev_word
# 255
# EOF
]
end

View file

@ -0,0 +1,519 @@
class Reline::KeyActor::ViCommand < Reline::KeyActor::Base
MAPPING = [
# 0 ^@
:ed_unassigned,
# 1 ^A
:ed_move_to_beg,
# 2 ^B
:ed_unassigned,
# 3 ^C
:ed_ignore,
# 4 ^D
:vi_end_of_transmission,
# 5 ^E
:ed_move_to_end,
# 6 ^F
:ed_unassigned,
# 7 ^G
:ed_unassigned,
# 8 ^H
:ed_delete_prev_char,
# 9 ^I
:ed_unassigned,
# 10 ^J
:ed_newline,
# 11 ^K
:ed_kill_line,
# 12 ^L
:ed_clear_screen,
# 13 ^M
:ed_newline,
# 14 ^N
:ed_next_history,
# 15 ^O
:ed_ignore,
# 16 ^P
:ed_prev_history,
# 17 ^Q
:ed_ignore,
# 18 ^R
:ed_redisplay,
# 19 ^S
:ed_ignore,
# 20 ^T
:ed_unassigned,
# 21 ^U
:vi_kill_line_prev,
# 22 ^V
:ed_quoted_insert,
# 23 ^W
:ed_delete_prev_word,
# 24 ^X
:ed_unassigned,
# 25 ^Y
:ed_unassigned,
# 26 ^Z
:ed_unassigned,
# 27 ^[
:em_meta_next,
# 28 ^\
:ed_ignore,
# 29 ^]
:ed_unassigned,
# 30 ^^
:ed_unassigned,
# 31 ^_
:ed_unassigned,
# 32 SPACE
:ed_next_char,
# 33 !
:ed_unassigned,
# 34 "
:ed_unassigned,
# 35 #
:vi_comment_out,
# 36 $
:ed_move_to_end,
# 37 %
:vi_match,
# 38 &
:ed_unassigned,
# 39 '
:ed_unassigned,
# 40 (
:ed_unassigned,
# 41 )
:ed_unassigned,
# 42 *
:ed_unassigned,
# 43 +
:ed_next_history,
# 44 ,
:vi_repeat_prev_char,
# 45 -
:ed_prev_history,
# 46 .
:vi_redo,
# 47 /
:vi_search_prev,
# 48 0
:vi_zero,
# 49 1
:ed_argument_digit,
# 50 2
:ed_argument_digit,
# 51 3
:ed_argument_digit,
# 52 4
:ed_argument_digit,
# 53 5
:ed_argument_digit,
# 54 6
:ed_argument_digit,
# 55 7
:ed_argument_digit,
# 56 8
:ed_argument_digit,
# 57 9
:ed_argument_digit,
# 58 :
:ed_command,
# 59 ;
:vi_repeat_next_char,
# 60 <
:ed_unassigned,
# 61 =
:ed_unassigned,
# 62 >
:ed_unassigned,
# 63 ?
:vi_search_next,
# 64 @
:vi_alias,
# 65 A
:vi_add_at_eol,
# 66 B
:vi_prev_big_word,
# 67 C
:vi_change_to_eol,
# 68 D
:ed_kill_line,
# 69 E
:vi_end_big_word,
# 70 F
:vi_prev_char,
# 71 G
:vi_to_history_line,
# 72 H
:ed_unassigned,
# 73 I
:vi_insert_at_bol,
# 74 J
:ed_search_next_history,
# 75 K
:ed_search_prev_history,
# 76 L
:ed_unassigned,
# 77 M
:ed_unassigned,
# 78 N
:vi_repeat_search_prev,
# 79 O
:ed_sequence_lead_in,
# 80 P
:vi_paste_prev,
# 81 Q
:ed_unassigned,
# 82 R
:vi_replace_mode,
# 83 S
:vi_substitute_line,
# 84 T
:vi_to_prev_char,
# 85 U
:vi_undo_line,
# 86 V
:ed_unassigned,
# 87 W
:vi_next_big_word,
# 88 X
:ed_delete_prev_char,
# 89 Y
:vi_yank_end,
# 90 Z
:ed_unassigned,
# 91 [
:ed_sequence_lead_in,
# 92 \
:ed_unassigned,
# 93 ]
:ed_unassigned,
# 94 ^
:ed_move_to_beg,
# 95 _
:vi_history_word,
# 96 `
:ed_unassigned,
# 97 a
:vi_add,
# 98 b
:vi_prev_word,
# 99 c
:vi_change_meta,
# 100 d
:vi_delete_meta,
# 101 e
:vi_end_word,
# 102 f
:vi_next_char,
# 103 g
:ed_unassigned,
# 104 h
:ed_prev_char,
# 105 i
:vi_insert,
# 106 j
:ed_next_history,
# 107 k
:ed_prev_history,
# 108 l
:ed_next_char,
# 109 m
:ed_unassigned,
# 110 n
:vi_repeat_search_next,
# 111 o
:ed_unassigned,
# 112 p
:vi_paste_next,
# 113 q
:ed_unassigned,
# 114 r
:vi_replace_char,
# 115 s
:vi_substitute_char,
# 116 t
:vi_to_next_char,
# 117 u
:vi_undo,
# 118 v
:vi_histedit,
# 119 w
:vi_next_word,
# 120 x
:ed_delete_next_char,
# 121 y
:vi_yank,
# 122 z
:ed_unassigned,
# 123 {
:ed_unassigned,
# 124 |
:vi_to_column,
# 125 }
:ed_unassigned,
# 126 ~
:vi_change_case,
# 127 ^?
:ed_delete_prev_char,
# 128 M-^@
:ed_unassigned,
# 129 M-^A
:ed_unassigned,
# 130 M-^B
:ed_unassigned,
# 131 M-^C
:ed_unassigned,
# 132 M-^D
:ed_unassigned,
# 133 M-^E
:ed_unassigned,
# 134 M-^F
:ed_unassigned,
# 135 M-^G
:ed_unassigned,
# 136 M-^H
:ed_unassigned,
# 137 M-^I
:ed_unassigned,
# 138 M-^J
:ed_unassigned,
# 139 M-^K
:ed_unassigned,
# 140 M-^L
:ed_unassigned,
# 141 M-^M
:ed_unassigned,
# 142 M-^N
:ed_unassigned,
# 143 M-^O
:ed_unassigned,
# 144 M-^P
:ed_unassigned,
# 145 M-^Q
:ed_unassigned,
# 146 M-^R
:ed_unassigned,
# 147 M-^S
:ed_unassigned,
# 148 M-^T
:ed_unassigned,
# 149 M-^U
:ed_unassigned,
# 150 M-^V
:ed_unassigned,
# 151 M-^W
:ed_unassigned,
# 152 M-^X
:ed_unassigned,
# 153 M-^Y
:ed_unassigned,
# 154 M-^Z
:ed_unassigned,
# 155 M-^[
:ed_unassigned,
# 156 M-^\
:ed_unassigned,
# 157 M-^]
:ed_unassigned,
# 158 M-^^
:ed_unassigned,
# 159 M-^_
:ed_unassigned,
# 160 M-SPACE
:ed_unassigned,
# 161 M-!
:ed_unassigned,
# 162 M-"
:ed_unassigned,
# 163 M-#
:ed_unassigned,
# 164 M-$
:ed_unassigned,
# 165 M-%
:ed_unassigned,
# 166 M-&
:ed_unassigned,
# 167 M-'
:ed_unassigned,
# 168 M-(
:ed_unassigned,
# 169 M-)
:ed_unassigned,
# 170 M-*
:ed_unassigned,
# 171 M-+
:ed_unassigned,
# 172 M-,
:ed_unassigned,
# 173 M--
:ed_unassigned,
# 174 M-.
:ed_unassigned,
# 175 M-/
:ed_unassigned,
# 176 M-0
:ed_unassigned,
# 177 M-1
:ed_unassigned,
# 178 M-2
:ed_unassigned,
# 179 M-3
:ed_unassigned,
# 180 M-4
:ed_unassigned,
# 181 M-5
:ed_unassigned,
# 182 M-6
:ed_unassigned,
# 183 M-7
:ed_unassigned,
# 184 M-8
:ed_unassigned,
# 185 M-9
:ed_unassigned,
# 186 M-:
:ed_unassigned,
# 187 M-;
:ed_unassigned,
# 188 M-<
:ed_unassigned,
# 189 M-=
:ed_unassigned,
# 190 M->
:ed_unassigned,
# 191 M-?
:ed_unassigned,
# 192 M-@
:ed_unassigned,
# 193 M-A
:ed_unassigned,
# 194 M-B
:ed_unassigned,
# 195 M-C
:ed_unassigned,
# 196 M-D
:ed_unassigned,
# 197 M-E
:ed_unassigned,
# 198 M-F
:ed_unassigned,
# 199 M-G
:ed_unassigned,
# 200 M-H
:ed_unassigned,
# 201 M-I
:ed_unassigned,
# 202 M-J
:ed_unassigned,
# 203 M-K
:ed_unassigned,
# 204 M-L
:ed_unassigned,
# 205 M-M
:ed_unassigned,
# 206 M-N
:ed_unassigned,
# 207 M-O
:ed_sequence_lead_in,
# 208 M-P
:ed_unassigned,
# 209 M-Q
:ed_unassigned,
# 210 M-R
:ed_unassigned,
# 211 M-S
:ed_unassigned,
# 212 M-T
:ed_unassigned,
# 213 M-U
:ed_unassigned,
# 214 M-V
:ed_unassigned,
# 215 M-W
:ed_unassigned,
# 216 M-X
:ed_unassigned,
# 217 M-Y
:ed_unassigned,
# 218 M-Z
:ed_unassigned,
# 219 M-[
:ed_sequence_lead_in,
# 220 M-\
:ed_unassigned,
# 221 M-]
:ed_unassigned,
# 222 M-^
:ed_unassigned,
# 223 M-_
:ed_unassigned,
# 223 M-`
:ed_unassigned,
# 224 M-a
:ed_unassigned,
# 225 M-b
:ed_unassigned,
# 226 M-c
:ed_unassigned,
# 227 M-d
:ed_unassigned,
# 228 M-e
:ed_unassigned,
# 229 M-f
:ed_unassigned,
# 230 M-g
:ed_unassigned,
# 231 M-h
:ed_unassigned,
# 232 M-i
:ed_unassigned,
# 233 M-j
:ed_unassigned,
# 234 M-k
:ed_unassigned,
# 235 M-l
:ed_unassigned,
# 236 M-m
:ed_unassigned,
# 237 M-n
:ed_unassigned,
# 238 M-o
:ed_unassigned,
# 239 M-p
:ed_unassigned,
# 240 M-q
:ed_unassigned,
# 241 M-r
:ed_unassigned,
# 242 M-s
:ed_unassigned,
# 243 M-t
:ed_unassigned,
# 244 M-u
:ed_unassigned,
# 245 M-v
:ed_unassigned,
# 246 M-w
:ed_unassigned,
# 247 M-x
:ed_unassigned,
# 248 M-y
:ed_unassigned,
# 249 M-z
:ed_unassigned,
# 250 M-{
:ed_unassigned,
# 251 M-|
:ed_unassigned,
# 252 M-}
:ed_unassigned,
# 253 M-~
:ed_unassigned,
# 254 M-^?
:ed_unassigned
# 255
# EOF
]
end

View file

@ -0,0 +1,518 @@
class Reline::KeyActor::ViInsert < Reline::KeyActor::Base
MAPPING = [
# 0 ^@
:ed_unassigned,
# 1 ^A
:ed_insert,
# 2 ^B
:ed_insert,
# 3 ^C
:ed_insert,
# 4 ^D
:vi_list_or_eof,
# 5 ^E
:ed_insert,
# 6 ^F
:ed_insert,
# 7 ^G
:ed_insert,
# 8 ^H
:vi_delete_prev_char,
# 9 ^I
:ed_insert,
# 10 ^J
:ed_newline,
# 11 ^K
:ed_insert,
# 12 ^L
:ed_insert,
# 13 ^M
:ed_newline,
# 14 ^N
:ed_insert,
# 15 ^O
:ed_insert,
# 16 ^P
:ed_insert,
# 17 ^Q
:ed_ignore,
# 18 ^R
:ed_insert,
# 19 ^S
:ed_ignore,
# 20 ^T
:ed_insert,
# 21 ^U
:vi_kill_line_prev,
# 22 ^V
:ed_quoted_insert,
# 23 ^W
:ed_delete_prev_word,
# 24 ^X
:ed_insert,
# 25 ^Y
:ed_insert,
# 26 ^Z
:ed_insert,
# 27 ^[
:vi_command_mode,
# 28 ^\
:ed_ignore,
# 29 ^]
:ed_insert,
# 30 ^^
:ed_insert,
# 31 ^_
:ed_insert,
# 32 SPACE
:ed_insert,
# 33 !
:ed_insert,
# 34 "
:ed_insert,
# 35 #
:ed_insert,
# 36 $
:ed_insert,
# 37 %
:ed_insert,
# 38 &
:ed_insert,
# 39 '
:ed_insert,
# 40 (
:ed_insert,
# 41 )
:ed_insert,
# 42 *
:ed_insert,
# 43 +
:ed_insert,
# 44 ,
:ed_insert,
# 45 -
:ed_insert,
# 46 .
:ed_insert,
# 47 /
:ed_insert,
# 48 0
:ed_insert,
# 49 1
:ed_insert,
# 50 2
:ed_insert,
# 51 3
:ed_insert,
# 52 4
:ed_insert,
# 53 5
:ed_insert,
# 54 6
:ed_insert,
# 55 7
:ed_insert,
# 56 8
:ed_insert,
# 57 9
:ed_insert,
# 58 :
:ed_insert,
# 59 ;
:ed_insert,
# 60 <
:ed_insert,
# 61 =
:ed_insert,
# 62 >
:ed_insert,
# 63 ?
:ed_insert,
# 64 @
:ed_insert,
# 65 A
:ed_insert,
# 66 B
:ed_insert,
# 67 C
:ed_insert,
# 68 D
:ed_insert,
# 69 E
:ed_insert,
# 70 F
:ed_insert,
# 71 G
:ed_insert,
# 72 H
:ed_insert,
# 73 I
:ed_insert,
# 74 J
:ed_insert,
# 75 K
:ed_insert,
# 76 L
:ed_insert,
# 77 M
:ed_insert,
# 78 N
:ed_insert,
# 79 O
:ed_insert,
# 80 P
:ed_insert,
# 81 Q
:ed_insert,
# 82 R
:ed_insert,
# 83 S
:ed_insert,
# 84 T
:ed_insert,
# 85 U
:ed_insert,
# 86 V
:ed_insert,
# 87 W
:ed_insert,
# 88 X
:ed_insert,
# 89 Y
:ed_insert,
# 90 Z
:ed_insert,
# 91 [
:ed_insert,
# 92 \
:ed_insert,
# 93 ]
:ed_insert,
# 94 ^
:ed_insert,
# 95 _
:ed_insert,
# 96 `
:ed_insert,
# 97 a
:ed_insert,
# 98 b
:ed_insert,
# 99 c
:ed_insert,
# 100 d
:ed_insert,
# 101 e
:ed_insert,
# 102 f
:ed_insert,
# 103 g
:ed_insert,
# 104 h
:ed_insert,
# 105 i
:ed_insert,
# 106 j
:ed_insert,
# 107 k
:ed_insert,
# 108 l
:ed_insert,
# 109 m
:ed_insert,
# 110 n
:ed_insert,
# 111 o
:ed_insert,
# 112 p
:ed_insert,
# 113 q
:ed_insert,
# 114 r
:ed_insert,
# 115 s
:ed_insert,
# 116 t
:ed_insert,
# 117 u
:ed_insert,
# 118 v
:ed_insert,
# 119 w
:ed_insert,
# 120 x
:ed_insert,
# 121 y
:ed_insert,
# 122 z
:ed_insert,
# 123 {
:ed_insert,
# 124 |
:ed_insert,
# 125 }
:ed_insert,
# 126 ~
:ed_insert,
# 127 ^?
:vi_delete_prev_char,
# 128 M-^@
:ed_insert,
# 129 M-^A
:ed_insert,
# 130 M-^B
:ed_insert,
# 131 M-^C
:ed_insert,
# 132 M-^D
:ed_insert,
# 133 M-^E
:ed_insert,
# 134 M-^F
:ed_insert,
# 135 M-^G
:ed_insert,
# 136 M-^H
:ed_insert,
# 137 M-^I
:ed_insert,
# 138 M-^J
:ed_insert,
# 139 M-^K
:ed_insert,
# 140 M-^L
:ed_insert,
# 141 M-^M
:ed_insert,
# 142 M-^N
:ed_insert,
# 143 M-^O
:ed_insert,
# 144 M-^P
:ed_insert,
# 145 M-^Q
:ed_insert,
# 146 M-^R
:ed_insert,
# 147 M-^S
:ed_insert,
# 148 M-^T
:ed_insert,
# 149 M-^U
:ed_insert,
# 150 M-^V
:ed_insert,
# 151 M-^W
:ed_insert,
# 152 M-^X
:ed_insert,
# 153 M-^Y
:ed_insert,
# 154 M-^Z
:ed_insert,
# 155 M-^[
:ed_insert,
# 156 M-^\
:ed_insert,
# 157 M-^]
:ed_insert,
# 158 M-^^
:ed_insert,
# 159 M-^_
:ed_insert,
# 160 M-SPACE
:ed_insert,
# 161 M-!
:ed_insert,
# 162 M-"
:ed_insert,
# 163 M-#
:ed_insert,
# 164 M-$
:ed_insert,
# 165 M-%
:ed_insert,
# 166 M-&
:ed_insert,
# 167 M-'
:ed_insert,
# 168 M-(
:ed_insert,
# 169 M-)
:ed_insert,
# 170 M-*
:ed_insert,
# 171 M-+
:ed_insert,
# 172 M-,
:ed_insert,
# 173 M--
:ed_insert,
# 174 M-.
:ed_insert,
# 175 M-/
:ed_insert,
# 176 M-0
:ed_insert,
# 177 M-1
:ed_insert,
# 178 M-2
:ed_insert,
# 179 M-3
:ed_insert,
# 180 M-4
:ed_insert,
# 181 M-5
:ed_insert,
# 182 M-6
:ed_insert,
# 183 M-7
:ed_insert,
# 184 M-8
:ed_insert,
# 185 M-9
:ed_insert,
# 186 M-:
:ed_insert,
# 187 M-;
:ed_insert,
# 188 M-<
:ed_insert,
# 189 M-=
:ed_insert,
# 190 M->
:ed_insert,
# 191 M-?
:ed_insert,
# 192 M-@
:ed_insert,
# 193 M-A
:ed_insert,
# 194 M-B
:ed_insert,
# 195 M-C
:ed_insert,
# 196 M-D
:ed_insert,
# 197 M-E
:ed_insert,
# 198 M-F
:ed_insert,
# 199 M-G
:ed_insert,
# 200 M-H
:ed_insert,
# 201 M-I
:ed_insert,
# 202 M-J
:ed_insert,
# 203 M-K
:ed_insert,
# 204 M-L
:ed_insert,
# 205 M-M
:ed_insert,
# 206 M-N
:ed_insert,
# 207 M-O
:ed_insert,
# 208 M-P
:ed_insert,
# 209 M-Q
:ed_insert,
# 210 M-R
:ed_insert,
# 211 M-S
:ed_insert,
# 212 M-T
:ed_insert,
# 213 M-U
:ed_insert,
# 214 M-V
:ed_insert,
# 215 M-W
:ed_insert,
# 216 M-X
:ed_insert,
# 217 M-Y
:ed_insert,
# 218 M-Z
:ed_insert,
# 219 M-[
:ed_insert,
# 220 M-\
:ed_insert,
# 221 M-]
:ed_insert,
# 222 M-^
:ed_insert,
# 223 M-_
:ed_insert,
# 223 M-`
:ed_insert,
# 224 M-a
:ed_insert,
# 225 M-b
:ed_insert,
# 226 M-c
:ed_insert,
# 227 M-d
:ed_insert,
# 228 M-e
:ed_insert,
# 229 M-f
:ed_insert,
# 230 M-g
:ed_insert,
# 231 M-h
:ed_insert,
# 232 M-i
:ed_insert,
# 233 M-j
:ed_insert,
# 234 M-k
:ed_insert,
# 235 M-l
:ed_insert,
# 236 M-m
:ed_insert,
# 237 M-n
:ed_insert,
# 238 M-o
:ed_insert,
# 239 M-p
:ed_insert,
# 240 M-q
:ed_insert,
# 241 M-r
:ed_insert,
# 242 M-s
:ed_insert,
# 243 M-t
:ed_insert,
# 244 M-u
:ed_insert,
# 245 M-v
:ed_insert,
# 246 M-w
:ed_insert,
# 247 M-x
:ed_insert,
# 248 M-y
:ed_insert,
# 249 M-z
:ed_insert,
# 250 M-{
:ed_insert,
# 251 M-|
:ed_insert,
# 252 M-}
:ed_insert,
# 253 M-~
:ed_insert,
# 254 M-^?
:ed_insert
# 255
# EOF
]
end

74
lib/reline/key_stroke.rb Normal file
View file

@ -0,0 +1,74 @@
class Reline::KeyStroke
using Module.new {
refine Array do
def start_with?(other)
other.size <= size && other == self.take(other.size)
end
def bytes
self
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

113
lib/reline/kill_ring.rb Normal file
View file

@ -0,0 +1,113 @@
class Reline::KillRing
module State
FRESH = :fresh
CONTINUED = :continued
PROCESSED = :processed
YANK = :yank
end
RingPoint = Struct.new(:backward, :forward, :str) do
def initialize(str)
super(nil, nil, str)
end
def ==(other)
object_id == other.object_id
end
end
class RingBuffer
attr_reader :size
attr_reader :head
def initialize(max = 1024)
@max = max
@size = 0
@head = nil # reading head of ring-shaped tape
end
def <<(point)
if @size.zero?
@head = point
@head.backward = @head
@head.forward = @head
@size = 1
elsif @size >= @max
tail = @head.forward
new_tail = tail.forward
@head.forward = point
point.backward = @head
new_tail.backward = point
point.forward = new_tail
@head = point
else
tail = @head.forward
@head.forward = point
point.backward = @head
tail.backward = point
point.forward = tail
@head = point
@size += 1
end
end
def empty?
@size.zero?
end
end
def initialize(max = 1024)
@ring = RingBuffer.new(max)
@ring_pointer = nil
@buffer = nil
@state = State::FRESH
end
def append(string, before_p = false)
case @state
when State::FRESH, State::YANK
@ring << RingPoint.new(string)
@state = State::CONTINUED
when State::CONTINUED, State::PROCESSED
if before_p
@ring.head.str.prepend(string)
else
@ring.head.str.concat(string)
end
@state = State::CONTINUED
end
end
def process
case @state
when State::FRESH
# nothing to do
when State::CONTINUED
@state = State::PROCESSED
when State::PROCESSED
@state = State::FRESH
when State::YANK
# nothing to do
end
end
def yank
unless @ring.empty?
@state = State::YANK
@ring_pointer = @ring.head
@ring_pointer.str
else
nil
end
end
def yank_pop
if @state == State::YANK
prev_yank = @ring_pointer.str
@ring_pointer = @ring_pointer.backward
[@ring_pointer.str, prev_yank]
else
nil
end
end
end

1358
lib/reline/line_editor.rb Normal file

File diff suppressed because it is too large Load diff

25
lib/reline/reline.gemspec Normal file
View file

@ -0,0 +1,25 @@
lib = File.expand_path('../lib', __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'reline/version'
Gem::Specification.new do |spec|
spec.name = 'reline'
spec.version = Reline::VERSION
spec.authors = ['aycabta']
spec.email = ['aycabta@gmail.com']
spec.summary = %q{Alternative GNU Readline or Editline implementation by pure Ruby.}
spec.description = %q{Alternative GNU Readline or Editline implementation by pure Ruby.}
spec.homepage = 'https://github.com/ruby/reline'
spec.license = 'Ruby License'
spec.files = Dir['BSDL', 'COPYING', 'README.md', 'lib/**/*']
spec.bindir = 'exe'
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
spec.require_paths = ['lib']
spec.add_development_dependency 'bundler'
spec.add_development_dependency 'rake'
spec.add_development_dependency 'test-unit'
end

415
lib/reline/unicode.rb Normal file
View file

@ -0,0 +1,415 @@
class Reline::Unicode
EscapedPairs = {
0x00 => '^@',
0x01 => '^A', # C-a
0x02 => '^B',
0x03 => '^C',
0x04 => '^D',
0x05 => '^E',
0x06 => '^F',
0x07 => '^G',
0x08 => '^H', # Backspace
0x09 => '^I',
0x0A => '^J',
0x0B => '^K',
0x0C => '^L',
0x0D => '^M', # Enter
0x0E => '^N',
0x0F => '^O',
0x10 => '^P',
0x11 => '^Q',
0x12 => '^R',
0x13 => '^S',
0x14 => '^T',
0x15 => '^U',
0x16 => '^V',
0x17 => '^W',
0x18 => '^X',
0x19 => '^Y',
0x1A => '^Z', # C-z
0x1B => '^[', # C-[ C-3
0x1D => '^]', # C-]
0x1E => '^^', # C-~ C-6
0x1F => '^_', # C-_ C-7
0x7F => '^?', # C-? C-8
}
EscapedChars = EscapedPairs.keys.map(&:chr)
def self.get_mbchar_byte_size_by_first_char(c)
# Checks UTF-8 character byte size
case c.ord
# 0b0xxxxxxx
when ->(code) { (code ^ 0b10000000).allbits?(0b10000000) } then 1
# 0b110xxxxx
when ->(code) { (code ^ 0b00100000).allbits?(0b11100000) } then 2
# 0b1110xxxx
when ->(code) { (code ^ 0b00010000).allbits?(0b11110000) } then 3
# 0b11110xxx
when ->(code) { (code ^ 0b00001000).allbits?(0b11111000) } then 4
# 0b111110xx
when ->(code) { (code ^ 0b00000100).allbits?(0b11111100) } then 5
# 0b1111110x
when ->(code) { (code ^ 0b00000010).allbits?(0b11111110) } then 6
# successor of mbchar
else 0
end
end
def self.get_mbchar_width(mbchar)
case mbchar.encode(Encoding::UTF_8)
when *EscapedChars # ^ + char, such as ^M, ^H, ^[, ...
2
when /^\u{2E3B}/ # THREE-EM DASH
3
when /^\p{M}/
0
when EastAsianWidth::TYPE_A
Reline.ambiguous_width
when EastAsianWidth::TYPE_F, EastAsianWidth::TYPE_W
2
when EastAsianWidth::TYPE_H, EastAsianWidth::TYPE_NA, EastAsianWidth::TYPE_N
1
else
nil
end
end
def self.get_next_mbchar_size(line, byte_pointer)
grapheme = line.byteslice(byte_pointer..-1).grapheme_clusters.first
grapheme ? grapheme.bytesize : 0
end
def self.get_prev_mbchar_size(line, byte_pointer)
if byte_pointer.zero?
0
else
grapheme = line.byteslice(0..(byte_pointer - 1)).grapheme_clusters.last
grapheme ? grapheme.bytesize : 0
end
end
def self.em_forward_word(line, byte_pointer)
width = 0
byte_size = 0
while line.bytesize > (byte_pointer + byte_size)
size = get_next_mbchar_size(line, byte_pointer + byte_size)
mbchar = line.byteslice(byte_pointer + byte_size, size)
break if mbchar.encode(Encoding::UTF_8) =~ /\p{Word}/
width += get_mbchar_width(mbchar)
byte_size += size
end
while line.bytesize > (byte_pointer + byte_size)
size = get_next_mbchar_size(line, byte_pointer + byte_size)
mbchar = line.byteslice(byte_pointer + byte_size, size)
break if mbchar.encode(Encoding::UTF_8) =~ /\P{Word}/
width += get_mbchar_width(mbchar)
byte_size += size
end
[byte_size, width]
end
def self.em_forward_word_with_capitalization(line, byte_pointer)
width = 0
byte_size = 0
new_str = String.new
while line.bytesize > (byte_pointer + byte_size)
size = get_next_mbchar_size(line, byte_pointer + byte_size)
mbchar = line.byteslice(byte_pointer + byte_size, size)
break if mbchar.encode(Encoding::UTF_8) =~ /\p{Word}/
new_str += mbchar
width += get_mbchar_width(mbchar)
byte_size += size
end
first = true
while line.bytesize > (byte_pointer + byte_size)
size = get_next_mbchar_size(line, byte_pointer + byte_size)
mbchar = line.byteslice(byte_pointer + byte_size, size)
break if mbchar.encode(Encoding::UTF_8) =~ /\P{Word}/
if first
new_str += mbchar.upcase
first = false
else
new_str += mbchar.downcase
end
width += get_mbchar_width(mbchar)
byte_size += size
end
[byte_size, width, new_str]
end
def self.em_backward_word(line, byte_pointer)
width = 0
byte_size = 0
while 0 < (byte_pointer - byte_size)
size = get_prev_mbchar_size(line, byte_pointer - byte_size)
mbchar = line.byteslice(byte_pointer - byte_size - size, size)
break if mbchar.encode(Encoding::UTF_8) =~ /\p{Word}/
width += get_mbchar_width(mbchar)
byte_size += size
end
while 0 < (byte_pointer - byte_size)
size = get_prev_mbchar_size(line, byte_pointer - byte_size)
mbchar = line.byteslice(byte_pointer - byte_size - size, size)
break if mbchar.encode(Encoding::UTF_8) =~ /\P{Word}/
width += get_mbchar_width(mbchar)
byte_size += size
end
[byte_size, width]
end
def self.em_big_backward_word(line, byte_pointer)
width = 0
byte_size = 0
while 0 < (byte_pointer - byte_size)
size = get_prev_mbchar_size(line, byte_pointer - byte_size)
mbchar = line.byteslice(byte_pointer - byte_size - size, size)
break if mbchar =~ /\S/
width += get_mbchar_width(mbchar)
byte_size += size
end
while 0 < (byte_pointer - byte_size)
size = get_prev_mbchar_size(line, byte_pointer - byte_size)
mbchar = line.byteslice(byte_pointer - byte_size - size, size)
break if mbchar =~ /\s/
width += get_mbchar_width(mbchar)
byte_size += size
end
[byte_size, width]
end
def self.vi_big_forward_word(line, byte_pointer)
width = 0
byte_size = 0
while (line.bytesize - 1) > (byte_pointer + byte_size)
size = get_next_mbchar_size(line, byte_pointer + byte_size)
mbchar = line.byteslice(byte_pointer + byte_size, size)
break if mbchar =~ /\s/
width += get_mbchar_width(mbchar)
byte_size += size
end
while (line.bytesize - 1) > (byte_pointer + byte_size)
size = get_next_mbchar_size(line, byte_pointer + byte_size)
mbchar = line.byteslice(byte_pointer + byte_size, size)
break if mbchar =~ /\S/
width += get_mbchar_width(mbchar)
byte_size += size
end
[byte_size, width]
end
def self.vi_big_forward_end_word(line, byte_pointer)
if (line.bytesize - 1) > byte_pointer
size = get_next_mbchar_size(line, byte_pointer)
mbchar = line.byteslice(byte_pointer, size)
width = get_mbchar_width(mbchar)
byte_size = size
else
return [0, 0]
end
while (line.bytesize - 1) > (byte_pointer + byte_size)
size = get_next_mbchar_size(line, byte_pointer + byte_size)
mbchar = line.byteslice(byte_pointer + byte_size, size)
break if mbchar =~ /\S/
width += get_mbchar_width(mbchar)
byte_size += size
end
prev_width = width
prev_byte_size = byte_size
while line.bytesize > (byte_pointer + byte_size)
size = get_next_mbchar_size(line, byte_pointer + byte_size)
mbchar = line.byteslice(byte_pointer + byte_size, size)
break if mbchar =~ /\s/
prev_width = width
prev_byte_size = byte_size
width += get_mbchar_width(mbchar)
byte_size += size
end
[prev_byte_size, prev_width]
end
def self.vi_big_backward_word(line, byte_pointer)
width = 0
byte_size = 0
while 0 < (byte_pointer - byte_size)
size = get_prev_mbchar_size(line, byte_pointer - byte_size)
mbchar = line.byteslice(byte_pointer - byte_size - size, size)
break if mbchar =~ /\S/
width += get_mbchar_width(mbchar)
byte_size += size
end
while 0 < (byte_pointer - byte_size)
size = get_prev_mbchar_size(line, byte_pointer - byte_size)
mbchar = line.byteslice(byte_pointer - byte_size - size, size)
break if mbchar =~ /\s/
width += get_mbchar_width(mbchar)
byte_size += size
end
[byte_size, width]
end
def self.vi_forward_word(line, byte_pointer)
if (line.bytesize - 1) > byte_pointer
size = get_next_mbchar_size(line, byte_pointer)
mbchar = line.byteslice(byte_pointer, size)
if mbchar =~ /\w/
started_by = :word
elsif mbchar =~ /\s/
started_by = :space
else
started_by = :non_word_printable
end
width = get_mbchar_width(mbchar)
byte_size = size
else
return [0, 0]
end
while (line.bytesize - 1) > (byte_pointer + byte_size)
size = get_next_mbchar_size(line, byte_pointer + byte_size)
mbchar = line.byteslice(byte_pointer + byte_size, size)
case started_by
when :word
break if mbchar =~ /\W/
when :space
break if mbchar =~ /\S/
when :non_word_printable
break if mbchar =~ /\w|\s/
end
width += get_mbchar_width(mbchar)
byte_size += size
end
while (line.bytesize - 1) > (byte_pointer + byte_size)
size = get_next_mbchar_size(line, byte_pointer + byte_size)
mbchar = line.byteslice(byte_pointer + byte_size, size)
break if mbchar =~ /\S/
width += get_mbchar_width(mbchar)
byte_size += size
end
[byte_size, width]
end
def self.vi_forward_end_word(line, byte_pointer)
if (line.bytesize - 1) > byte_pointer
size = get_next_mbchar_size(line, byte_pointer)
mbchar = line.byteslice(byte_pointer, size)
if mbchar =~ /\w/
started_by = :word
elsif mbchar =~ /\s/
started_by = :space
else
started_by = :non_word_printable
end
width = get_mbchar_width(mbchar)
byte_size = size
else
return [0, 0]
end
if (line.bytesize - 1) > (byte_pointer + byte_size)
size = get_next_mbchar_size(line, byte_pointer + byte_size)
mbchar = line.byteslice(byte_pointer + byte_size, size)
if mbchar =~ /\w/
second = :word
elsif mbchar =~ /\s/
second = :space
else
second = :non_word_printable
end
second_width = get_mbchar_width(mbchar)
second_byte_size = size
else
return [byte_size, width]
end
if second == :space
width += second_width
byte_size += second_byte_size
while (line.bytesize - 1) > (byte_pointer + byte_size)
size = get_next_mbchar_size(line, byte_pointer + byte_size)
mbchar = line.byteslice(byte_pointer + byte_size, size)
if mbchar =~ /\S/
if mbchar =~ /\w/
started_by = :word
else
started_by = :non_word_printable
end
break
end
width += get_mbchar_width(mbchar)
byte_size += size
end
else
case [started_by, second]
when [:word, :non_word_printable], [:non_word_printable, :word]
started_by = second
else
width += second_width
byte_size += second_byte_size
started_by = second
end
end
prev_width = width
prev_byte_size = byte_size
while line.bytesize > (byte_pointer + byte_size)
size = get_next_mbchar_size(line, byte_pointer + byte_size)
mbchar = line.byteslice(byte_pointer + byte_size, size)
case started_by
when :word
break if mbchar =~ /\W/
when :non_word_printable
break if mbchar =~ /[\w\s]/
end
prev_width = width
prev_byte_size = byte_size
width += get_mbchar_width(mbchar)
byte_size += size
end
[prev_byte_size, prev_width]
end
def self.vi_backward_word(line, byte_pointer)
width = 0
byte_size = 0
while 0 < (byte_pointer - byte_size)
size = get_prev_mbchar_size(line, byte_pointer - byte_size)
mbchar = line.byteslice(byte_pointer - byte_size - size, size)
if mbchar =~ /\S/
if mbchar =~ /\w/
started_by = :word
else
started_by = :non_word_printable
end
break
end
width += get_mbchar_width(mbchar)
byte_size += size
end
while 0 < (byte_pointer - byte_size)
size = get_prev_mbchar_size(line, byte_pointer - byte_size)
mbchar = line.byteslice(byte_pointer - byte_size - size, size)
case started_by
when :word
break if mbchar =~ /\W/
when :non_word_printable
break if mbchar =~ /[\w\s]/
end
width += get_mbchar_width(mbchar)
byte_size += size
end
[byte_size, width]
end
def self.ed_move_to_begin(line)
width = 0
byte_size = 0
while (line.bytesize - 1) > byte_size
size = get_next_mbchar_size(line, byte_size)
mbchar = line.byteslice(byte_size, size)
if mbchar =~ /\S/
break
end
width += get_mbchar_width(mbchar)
byte_size += size
end
[byte_size, width]
end
end
require 'reline/unicode/east_asian_width'

File diff suppressed because it is too large Load diff

3
lib/reline/version.rb Normal file
View file

@ -0,0 +1,3 @@
module Reline
VERSION = '0.0.0'
end

174
lib/reline/windows.rb Normal file
View file

@ -0,0 +1,174 @@
require 'fiddle/import'
class Win32API
DLL = {}
TYPEMAP = {"0" => Fiddle::TYPE_VOID, "S" => Fiddle::TYPE_VOIDP, "I" => Fiddle::TYPE_LONG}
POINTER_TYPE = Fiddle::SIZEOF_VOIDP == Fiddle::SIZEOF_LONG_LONG ? 'q*' : 'l!*'
WIN32_TYPES = "VPpNnLlIi"
DL_TYPES = "0SSI"
def initialize(dllname, func, import, export = "0", calltype = :stdcall)
@proto = [import].join.tr(WIN32_TYPES, DL_TYPES).sub(/^(.)0*$/, '\1')
import = @proto.chars.map {|win_type| TYPEMAP[win_type.tr(WIN32_TYPES, DL_TYPES)]}
export = TYPEMAP[export.tr(WIN32_TYPES, DL_TYPES)]
calltype = Fiddle::Importer.const_get(:CALL_TYPE_TO_ABI)[calltype]
handle = DLL[dllname] ||=
begin
Fiddle.dlopen(dllname)
rescue Fiddle::DLError
raise unless File.extname(dllname).empty?
Fiddle.dlopen(dllname + ".dll")
end
@func = Fiddle::Function.new(handle[func], import, export, calltype)
rescue Fiddle::DLError => e
raise LoadError, e.message, e.backtrace
end
def call(*args)
import = @proto.split("")
args.each_with_index do |x, i|
args[i], = [x == 0 ? nil : x].pack("p").unpack(POINTER_TYPE) if import[i] == "S"
args[i], = [x].pack("I").unpack("i") if import[i] == "I"
end
ret, = @func.call(*args)
return ret || 0
end
alias Call call
end
module Reline
VK_LMENU = 0xA4
STD_OUTPUT_HANDLE = -11
@@getwch = Win32API.new('msvcrt', '_getwch', [], 'I')
@@kbhit = Win32API.new('msvcrt', '_kbhit', [], 'I')
@@GetKeyState = Win32API.new('user32', 'GetKeyState', ['L'], 'L')
@@GetConsoleScreenBufferInfo = Win32API.new('kernel32', 'GetConsoleScreenBufferInfo', ['L', 'P'], 'L')
@@SetConsoleCursorPosition = Win32API.new('kernel32', 'SetConsoleCursorPosition', ['L', 'L'], 'L')
@@GetStdHandle = Win32API.new('kernel32', 'GetStdHandle', ['L'], 'L')
@@FillConsoleOutputCharacter = Win32API.new('kernel32', 'FillConsoleOutputCharacter', ['L', 'L', 'L', 'L', 'P'], 'L')
@@ScrollConsoleScreenBuffer = Win32API.new('kernel32', 'ScrollConsoleScreenBuffer', ['L', 'P', 'P', 'L', 'P'], 'L')
@@hConsoleHandle = @@GetStdHandle.call(STD_OUTPUT_HANDLE)
@@buf = []
def getwch
while @@kbhit.call == 0
sleep(0.001)
end
result = []
until @@kbhit.call == 0
ret = @@getwch.call
begin
result.concat(ret.chr(Encoding::UTF_8).encode(Encoding.default_external).bytes)
rescue Encoding::UndefinedConversionError
result << ret
result << @@getwch.call if ret == 224
end
end
result
end
def getc
unless @@buf.empty?
return @@buf.shift
end
input = getwch
alt = (@@GetKeyState.call(VK_LMENU) & 0x80) != 0
if input.size > 1
@@buf.concat(input)
else # single byte
case input[0]
when 0x00
getwch
alt = false
input = getwch
@@buf.concat(input)
when 0xE0
@@buf.concat(input)
input = getwch
@@buf.concat(input)
when 0x03
@@buf.concat(input)
else
@@buf.concat(input)
end
end
if alt
"\e".ord
else
@@buf.shift
end
end
def self.get_screen_size
csbi = 0.chr * 24
@@GetConsoleScreenBufferInfo.call(@@hConsoleHandle, csbi)
csbi[0, 4].unpack('SS')
end
def self.cursor_pos
csbi = 0.chr * 24
@@GetConsoleScreenBufferInfo.call(@@hConsoleHandle, csbi)
x = csbi[4, 2].unpack('s*').first
y = csbi[6, 4].unpack('s*').first
CursorPos.new(x, y)
end
def self.move_cursor_column(val)
@@SetConsoleCursorPosition.call(@@hConsoleHandle, cursor_pos.y * 65536 + val)
end
def self.move_cursor_up(val)
if val > 0
@@SetConsoleCursorPosition.call(@@hConsoleHandle, (cursor_pos.y - val) * 65536 + cursor_pos.x)
elsif val < 0
move_cursor_down(-val)
end
end
def self.move_cursor_down(val)
if val > 0
@@SetConsoleCursorPosition.call(@@hConsoleHandle, (cursor_pos.y + val) * 65536 + cursor_pos.x)
elsif val < 0
move_cursor_up(-val)
end
end
def self.erase_after_cursor
csbi = 0.chr * 24
@@GetConsoleScreenBufferInfo.call(@@hConsoleHandle, csbi)
cursor = csbi[4, 4].unpack('L').first
written = 0.chr * 4
@@FillConsoleOutputCharacter.call(@@hConsoleHandle, 0x20, get_screen_size.first - cursor_pos.x, cursor, written)
end
def self.scroll_down(val)
return if val.zero?
scroll_rectangle = [0, val, get_screen_size.last, get_screen_size.first].pack('s4')
destination_origin = 0 # y * 65536 + x
fill = [' '.ord, 0].pack('SS')
@@ScrollConsoleScreenBuffer.call(@@hConsoleHandle, scroll_rectangle, nil, destination_origin, fill)
end
def self.clear_screen
# TODO: Use FillConsoleOutputCharacter and FillConsoleOutputAttribute
print "\e[2J"
print "\e[1;1H"
end
def self.set_screen_size(rows, columns)
raise NotImplementedError
end
def prep
# do nothing
nil
end
def deprep(otio)
# do nothing
end
end

View file

@ -4,8 +4,8 @@ begin
require 'readline'
rescue LoadError
else
# rb-readline behaves quite differently
unless defined?(RbReadline)
# rb-readline and reline behave quite differently
unless defined?(RbReadline) or defined?(Reline)
MSpec.enable_feature :readline
end
end

73
test/reline/helper.rb Normal file
View file

@ -0,0 +1,73 @@
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
require 'reline'
require 'test/unit'
RELINE_TEST_ENCODING ||=
if ENV['RELINE_TEST_ENCODING']
Encoding.find(ENV['RELINE_TEST_ENCODING'])
else
Encoding.default_external
end
class Reline::TestCase < Test::Unit::TestCase
puts "Test encoding is #{RELINE_TEST_ENCODING}"
private def convert_str(input, options = {}, normalized = nil)
return nil if input.nil?
input.chars.map { |c|
if Reline::Unicode::EscapedChars.include?(c.ord)
c
else
c.encode(@line_editor.instance_variable_get(:@encoding), Encoding::UTF_8, options)
end
}.join
rescue Encoding::UndefinedConversionError, Encoding::InvalidByteSequenceError
input.unicode_normalize!(:nfc)
if normalized
options[:undef] = :replace
options[:replace] = '?'
end
normalized = true
retry
end
def input_keys(input, convert = true)
input = convert_str(input) if convert
input.chars.each do |c|
if c.bytesize == 1
eighth_bit = 0b10000000
byte = c.bytes.first
if byte.allbits?(eighth_bit)
@line_editor.input_key("\e".ord)
byte ^= eighth_bit
end
@line_editor.input_key(byte)
else
c.bytes.each do |b|
@line_editor.input_key(b)
end
end
end
end
def assert_line(expected)
expected = convert_str(expected)
assert_equal(expected, @line_editor.line)
end
def assert_byte_pointer_size(expected)
expected = convert_str(expected)
byte_pointer = @line_editor.instance_variable_get(:@byte_pointer)
assert_equal(
expected.bytesize, byte_pointer,
"<#{expected.inspect}> expected but was\n<#{@line_editor.line.byteslice(0, byte_pointer).inspect}>")
end
def assert_cursor(expected)
assert_equal(expected, @line_editor.instance_variable_get(:@cursor))
end
def assert_cursor_max(expected)
assert_equal(expected, @line_editor.instance_variable_get(:@cursor_max))
end
end

118
test/reline/test_config.rb Normal file
View file

@ -0,0 +1,118 @@
require_relative 'helper'
class Reline::Config::Test < Reline::TestCase
def setup
@pwd = Dir.pwd
@tmpdir = File.join(Dir.tmpdir, "test_reline_config_#{$$}")
begin
Dir.mkdir(@tmpdir)
rescue Errno::EEXIST
FileUtils.rm_rf(@tmpdir)
Dir.mkdir(@tmpdir)
end
Dir.chdir(@tmpdir)
@config = Reline::Config.new
end
def teardown
Dir.chdir(@pwd)
FileUtils.rm_rf(@tmpdir)
end
def test_read_lines
@config.read_lines(<<~LINES.split(/(?<=\n)/))
set bell-style on
LINES
assert_equal :audible, @config.instance_variable_get(:@bell_style)
end
def test_bind_key
key, func = @config.bind_key('"input"', '"abcde"')
assert_equal 'input', key
assert_equal 'abcde', func
end
def test_bind_key_with_macro
key, func = @config.bind_key('"input"', 'abcde')
assert_equal 'input', key
assert_equal :abcde, func
end
def test_bind_key_with_escaped_chars
assert_equal ['input', "\e \\ \" ' \a \b \d \f \n \r \t \v"], @config.bind_key('"input"', '"\\e \\\\ \\" \\\' \\a \\b \\d \\f \\n \\r \\t \\v"')
end
def test_bind_key_with_ctrl_chars
assert_equal ['input', "\C-h\C-h"], @config.bind_key('"input"', '"\C-h\C-H"')
end
def test_bind_key_with_meta_chars
assert_equal ['input', "\M-h\M-H".force_encoding('ASCII-8BIT')], @config.bind_key('"input"', '"\M-h\M-H"')
end
def test_bind_key_with_octal_number
assert_equal ['input', "\1"], @config.bind_key('"input"', '"\1"')
assert_equal ['input', "\12"], @config.bind_key('"input"', '"\12"')
assert_equal ['input', "\123"], @config.bind_key('"input"', '"\123"')
assert_equal ['input', ["\123", '4'].join], @config.bind_key('"input"', '"\1234"')
end
def test_bind_key_with_hexadecimal_number
assert_equal ['input', "\x4"], @config.bind_key('"input"', '"\x4"')
assert_equal ['input', "\x45"], @config.bind_key('"input"', '"\x45"')
assert_equal ['input', ["\x45", '6'].join], @config.bind_key('"input"', '"\x456"')
end
def test_include
File.open('included_partial', 'wt') do |f|
f.write(<<~PARTIAL_LINES)
set bell-style on
PARTIAL_LINES
end
@config.read_lines(<<~LINES.split(/(?<=\n)/))
$include included_partial
LINES
assert_equal :audible, @config.instance_variable_get(:@bell_style)
end
def test_if
@config.read_lines(<<~LINES.split(/(?<=\n)/))
$if Ruby
set bell-style audible
$else
set bell-style visible
$endif
LINES
assert_equal :audible, @config.instance_variable_get(:@bell_style)
end
def test_if_with_false
@config.read_lines(<<~LINES.split(/(?<=\n)/))
$if Python
set bell-style audible
$else
set bell-style visible
$endif
LINES
assert_equal :visible, @config.instance_variable_get(:@bell_style)
end
def test_if_with_indent
@config.read_lines(<<~LINES.split(/(?<=\n)/))
set bell-style none
$if Ruby
set bell-style audible
$else
set bell-style visible
$endif
LINES
assert_equal :audible, @config.instance_variable_get(:@bell_style)
end
end

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,51 @@
require_relative '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

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,256 @@
require_relative 'helper'
class Reline::KillRing::Test < Reline::TestCase
def setup
@prompt = '> '
@kill_ring = Reline::KillRing.new
end
def test_append_one
assert_equal(Reline::KillRing::State::FRESH, @kill_ring.instance_variable_get(:@state))
@kill_ring.append('a')
assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state))
@kill_ring.process
assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state))
assert_equal('a', @kill_ring.yank)
assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
assert_equal('a', @kill_ring.yank)
assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
assert_equal(['a', 'a'], @kill_ring.yank_pop)
assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
assert_equal(['a', 'a'], @kill_ring.yank_pop)
assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
end
def test_append_two
assert_equal(Reline::KillRing::State::FRESH, @kill_ring.instance_variable_get(:@state))
@kill_ring.append('a')
assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state))
@kill_ring.process
assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state))
@kill_ring.process
assert_equal(Reline::KillRing::State::FRESH, @kill_ring.instance_variable_get(:@state))
@kill_ring.append('b')
assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state))
@kill_ring.process
assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state))
assert_equal('b', @kill_ring.yank)
assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
assert_equal('b', @kill_ring.yank)
assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
assert_equal(['a', 'b'], @kill_ring.yank_pop)
assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
assert_equal(['b', 'a'], @kill_ring.yank_pop)
assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
end
def test_append_three
assert_equal(Reline::KillRing::State::FRESH, @kill_ring.instance_variable_get(:@state))
@kill_ring.append('a')
assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state))
@kill_ring.process
assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state))
@kill_ring.process
assert_equal(Reline::KillRing::State::FRESH, @kill_ring.instance_variable_get(:@state))
@kill_ring.append('b')
assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state))
@kill_ring.process
assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state))
@kill_ring.process
assert_equal(Reline::KillRing::State::FRESH, @kill_ring.instance_variable_get(:@state))
@kill_ring.append('c')
assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state))
@kill_ring.process
assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state))
assert_equal('c', @kill_ring.yank)
assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
assert_equal('c', @kill_ring.yank)
assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
assert_equal(['b', 'c'], @kill_ring.yank_pop)
assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
assert_equal(['a', 'b'], @kill_ring.yank_pop)
assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
assert_equal(['c', 'a'], @kill_ring.yank_pop)
assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
end
def test_append_three_with_max_two
@kill_ring = Reline::KillRing.new(2)
assert_equal(Reline::KillRing::State::FRESH, @kill_ring.instance_variable_get(:@state))
@kill_ring.append('a')
assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state))
@kill_ring.process
assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state))
@kill_ring.process
assert_equal(Reline::KillRing::State::FRESH, @kill_ring.instance_variable_get(:@state))
@kill_ring.append('b')
assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state))
@kill_ring.process
assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state))
@kill_ring.process
assert_equal(Reline::KillRing::State::FRESH, @kill_ring.instance_variable_get(:@state))
@kill_ring.append('c')
assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state))
@kill_ring.process
assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state))
assert_equal('c', @kill_ring.yank)
assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
assert_equal('c', @kill_ring.yank)
assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
assert_equal(['b', 'c'], @kill_ring.yank_pop)
assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
assert_equal(['c', 'b'], @kill_ring.yank_pop)
assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
assert_equal(['b', 'c'], @kill_ring.yank_pop)
assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
end
def test_append_four_with_max_two
@kill_ring = Reline::KillRing.new(2)
assert_equal(Reline::KillRing::State::FRESH, @kill_ring.instance_variable_get(:@state))
@kill_ring.append('a')
assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state))
@kill_ring.process
assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state))
@kill_ring.process
assert_equal(Reline::KillRing::State::FRESH, @kill_ring.instance_variable_get(:@state))
@kill_ring.append('b')
assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state))
@kill_ring.process
assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state))
@kill_ring.process
assert_equal(Reline::KillRing::State::FRESH, @kill_ring.instance_variable_get(:@state))
@kill_ring.append('c')
assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state))
@kill_ring.process
assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state))
@kill_ring.process
assert_equal(Reline::KillRing::State::FRESH, @kill_ring.instance_variable_get(:@state))
@kill_ring.append('d')
assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state))
@kill_ring.process
assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state))
assert_equal('d', @kill_ring.yank)
assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
assert_equal('d', @kill_ring.yank)
assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
assert_equal(['c', 'd'], @kill_ring.yank_pop)
assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
assert_equal(['d', 'c'], @kill_ring.yank_pop)
assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
assert_equal(['c', 'd'], @kill_ring.yank_pop)
assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
end
def test_append_after
assert_equal(Reline::KillRing::State::FRESH, @kill_ring.instance_variable_get(:@state))
@kill_ring.append('a')
assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state))
@kill_ring.process
assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state))
@kill_ring.append('b')
assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state))
@kill_ring.process
assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state))
assert_equal('ab', @kill_ring.yank)
assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
assert_equal('ab', @kill_ring.yank)
assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
assert_equal(['ab', 'ab'], @kill_ring.yank_pop)
assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
assert_equal(['ab', 'ab'], @kill_ring.yank_pop)
assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
end
def test_append_before
assert_equal(Reline::KillRing::State::FRESH, @kill_ring.instance_variable_get(:@state))
@kill_ring.append('a')
assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state))
@kill_ring.process
assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state))
@kill_ring.append('b', true)
assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state))
@kill_ring.process
assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state))
assert_equal('ba', @kill_ring.yank)
assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
assert_equal('ba', @kill_ring.yank)
assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
assert_equal(['ba', 'ba'], @kill_ring.yank_pop)
assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
assert_equal(['ba', 'ba'], @kill_ring.yank_pop)
assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
end
def test_append_chain_two
assert_equal(Reline::KillRing::State::FRESH, @kill_ring.instance_variable_get(:@state))
@kill_ring.append('a')
assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state))
@kill_ring.process
assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state))
@kill_ring.append('b')
assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state))
@kill_ring.process
assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state))
@kill_ring.process
assert_equal(Reline::KillRing::State::FRESH, @kill_ring.instance_variable_get(:@state))
@kill_ring.append('c')
assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state))
@kill_ring.process
assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state))
@kill_ring.append('d')
assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state))
@kill_ring.process
assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state))
assert_equal('cd', @kill_ring.yank)
assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
assert_equal('cd', @kill_ring.yank)
assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
assert_equal(['ab', 'cd'], @kill_ring.yank_pop)
assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
assert_equal(['cd', 'ab'], @kill_ring.yank_pop)
assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
end
def test_append_complex_chain
assert_equal(Reline::KillRing::State::FRESH, @kill_ring.instance_variable_get(:@state))
@kill_ring.append('c')
assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state))
@kill_ring.process
assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state))
@kill_ring.append('d')
assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state))
@kill_ring.process
assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state))
@kill_ring.append('b', true)
assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state))
@kill_ring.process
assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state))
@kill_ring.append('e')
assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state))
@kill_ring.process
assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state))
@kill_ring.append('a', true)
assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state))
@kill_ring.process
assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state))
@kill_ring.process
assert_equal(Reline::KillRing::State::FRESH, @kill_ring.instance_variable_get(:@state))
@kill_ring.append('A')
assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state))
@kill_ring.process
assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state))
@kill_ring.append('B')
assert_equal(Reline::KillRing::State::CONTINUED, @kill_ring.instance_variable_get(:@state))
@kill_ring.process
assert_equal(Reline::KillRing::State::PROCESSED, @kill_ring.instance_variable_get(:@state))
assert_equal('AB', @kill_ring.yank)
assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
assert_equal('AB', @kill_ring.yank)
assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
assert_equal(['abcde', 'AB'], @kill_ring.yank_pop)
assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
assert_equal(['AB', 'abcde'], @kill_ring.yank_pop)
assert_equal(Reline::KillRing::State::YANK, @kill_ring.instance_variable_get(:@state))
end
end

View file

@ -3,6 +3,7 @@
# * https://github.com/rubygems/rubygems
# * https://github.com/bundler/bundler
# * https://github.com/ruby/rdoc
# * https://github.com/ruby/reline
# * https://github.com/flori/json
# * https://github.com/ruby/psych
# * https://github.com/ruby/fileutils
@ -42,6 +43,7 @@ $repositories = {
rubygems: 'rubygems/rubygems',
bundler: 'bundler/bundler',
rdoc: 'ruby/rdoc',
reline: 'ruby/reline',
json: 'flori/json',
psych: 'ruby/psych',
fileutils: 'ruby/fileutils',
@ -102,6 +104,11 @@ def sync_default_gems(gem)
`cp -rf ../rdoc/exe/ri ./libexec`
`rm -f lib/rdoc/markdown.kpeg lib/rdoc/markdown/literals.kpeg lib/rdoc/rd/block_parser.ry lib/rdoc/rd/inline_parser.ry`
`git checkout lib/rdoc/.document`
when "reline"
`rm -rf lib/reline* test/reline`
`cp -rf ../reline/lib/reline* ./lib`
`cp -rf ../reline/test test/reline`
`cp ../reline/reline.gemspec ./lib/reline`
when "json"
`rm -rf ext/json test/json`
`cp -rf ../../flori/json/ext/json/ext ext/json`