From 17350c7e5534c8678097d70698fe08614a6c3997 Mon Sep 17 00:00:00 2001 From: aycabta Date: Sat, 27 Apr 2019 14:53:09 +0900 Subject: [PATCH] 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. --- appveyor.yml | 4 +- lib/readline.rb | 6 + lib/reline.rb | 197 +++ lib/reline/ansi.rb | 87 ++ lib/reline/config.rb | 235 ++++ lib/reline/key_actor.rb | 7 + lib/reline/key_actor/base.rb | 7 + lib/reline/key_actor/emacs.rb | 518 ++++++++ lib/reline/key_actor/vi_command.rb | 519 ++++++++ lib/reline/key_actor/vi_insert.rb | 518 ++++++++ lib/reline/key_stroke.rb | 74 ++ lib/reline/kill_ring.rb | 113 ++ lib/reline/line_editor.rb | 1358 +++++++++++++++++++++ lib/reline/reline.gemspec | 25 + lib/reline/unicode.rb | 415 +++++++ lib/reline/unicode/east_asian_width.rb | 1145 +++++++++++++++++ lib/reline/version.rb | 3 + lib/reline/windows.rb | 174 +++ spec/ruby/library/readline/spec_helper.rb | 4 +- test/reline/helper.rb | 73 ++ test/reline/test_config.rb | 118 ++ test/reline/test_key_actor_emacs.rb | 1166 ++++++++++++++++++ test/reline/test_key_stroke.rb | 51 + test/reline/test_key_vi_emacs.rb | 1026 ++++++++++++++++ test/reline/test_kill_ring.rb | 256 ++++ tool/sync_default_gems.rb | 7 + 26 files changed, 8103 insertions(+), 3 deletions(-) create mode 100644 lib/readline.rb create mode 100644 lib/reline.rb create mode 100644 lib/reline/ansi.rb create mode 100644 lib/reline/config.rb create mode 100644 lib/reline/key_actor.rb create mode 100644 lib/reline/key_actor/base.rb create mode 100644 lib/reline/key_actor/emacs.rb create mode 100644 lib/reline/key_actor/vi_command.rb create mode 100644 lib/reline/key_actor/vi_insert.rb create mode 100644 lib/reline/key_stroke.rb create mode 100644 lib/reline/kill_ring.rb create mode 100644 lib/reline/line_editor.rb create mode 100644 lib/reline/reline.gemspec create mode 100644 lib/reline/unicode.rb create mode 100644 lib/reline/unicode/east_asian_width.rb create mode 100644 lib/reline/version.rb create mode 100644 lib/reline/windows.rb create mode 100644 test/reline/helper.rb create mode 100644 test/reline/test_config.rb create mode 100644 test/reline/test_key_actor_emacs.rb create mode 100644 test/reline/test_key_stroke.rb create mode 100644 test/reline/test_key_vi_emacs.rb create mode 100644 test/reline/test_kill_ring.rb diff --git a/appveyor.yml b/appveyor.yml index a369d86a2c8eff..d0ab119377ea1d 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -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. diff --git a/lib/readline.rb b/lib/readline.rb new file mode 100644 index 00000000000000..690441e05c1571 --- /dev/null +++ b/lib/readline.rb @@ -0,0 +1,6 @@ +begin + require 'readline.so' +rescue LoadError + require 'reline' + Readline = Reline +end diff --git a/lib/reline.rb b/lib/reline.rb new file mode 100644 index 00000000000000..90919bdd2c31a1 --- /dev/null +++ b/lib/reline.rb @@ -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 diff --git a/lib/reline/ansi.rb b/lib/reline/ansi.rb new file mode 100644 index 00000000000000..f34c4207e5e21b --- /dev/null +++ b/lib/reline/ansi.rb @@ -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(/(?\d+);(?\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 diff --git a/lib/reline/config.rb b/lib/reline/config.rb new file mode 100644 index 00000000000000..0800dfd30f4e78 --- /dev/null +++ b/lib/reline/config.rb @@ -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 diff --git a/lib/reline/key_actor.rb b/lib/reline/key_actor.rb new file mode 100644 index 00000000000000..ebe09d20099f67 --- /dev/null +++ b/lib/reline/key_actor.rb @@ -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' diff --git a/lib/reline/key_actor/base.rb b/lib/reline/key_actor/base.rb new file mode 100644 index 00000000000000..f4abac55d49546 --- /dev/null +++ b/lib/reline/key_actor/base.rb @@ -0,0 +1,7 @@ +class Reline::KeyActor::Base + MAPPING = Array.new(256) + + def get_method(key) + self.class::MAPPING[key] + end +end diff --git a/lib/reline/key_actor/emacs.rb b/lib/reline/key_actor/emacs.rb new file mode 100644 index 00000000000000..0836cd340c530f --- /dev/null +++ b/lib/reline/key_actor/emacs.rb @@ -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 diff --git a/lib/reline/key_actor/vi_command.rb b/lib/reline/key_actor/vi_command.rb new file mode 100644 index 00000000000000..724f459011c0ce --- /dev/null +++ b/lib/reline/key_actor/vi_command.rb @@ -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 + diff --git a/lib/reline/key_actor/vi_insert.rb b/lib/reline/key_actor/vi_insert.rb new file mode 100644 index 00000000000000..8585a642ab806f --- /dev/null +++ b/lib/reline/key_actor/vi_insert.rb @@ -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 diff --git a/lib/reline/key_stroke.rb b/lib/reline/key_stroke.rb new file mode 100644 index 00000000000000..ac0a8207594390 --- /dev/null +++ b/lib/reline/key_stroke.rb @@ -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 diff --git a/lib/reline/kill_ring.rb b/lib/reline/kill_ring.rb new file mode 100644 index 00000000000000..842fd04697c481 --- /dev/null +++ b/lib/reline/kill_ring.rb @@ -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 diff --git a/lib/reline/line_editor.rb b/lib/reline/line_editor.rb new file mode 100644 index 00000000000000..7fe323c63e0541 --- /dev/null +++ b/lib/reline/line_editor.rb @@ -0,0 +1,1358 @@ +require 'reline/kill_ring' +require 'reline/unicode' + +require 'tempfile' +require 'pathname' + +class Reline::LineEditor + # TODO: undo + attr_reader :line + attr_accessor :confirm_multiline_termination_proc + attr_accessor :completion_proc + attr_accessor :dig_perfect_match_proc + attr_writer :retrieve_completion_block + + ARGUMENTABLE = %i{ + ed_delete_next_char + ed_delete_prev_char + ed_delete_prev_word + ed_next_char + ed_next_history + ed_next_line# + ed_prev_char + ed_prev_history + ed_prev_line# + ed_prev_word + ed_quoted_insert + vi_to_column + vi_next_word + vi_prev_word + vi_end_word + vi_next_big_word + vi_prev_big_word + vi_end_big_word + vi_next_char + vi_delete_meta + vi_paste_prev + vi_paste_next + vi_replace_char + } + + VI_OPERATORS = %i{ + vi_change_meta + vi_delete_meta + vi_yank + } + + VI_MOTIONS = %i{ + ed_prev_char + ed_next_char + vi_zero + ed_move_to_beg + ed_move_to_end + vi_to_column + vi_next_char + vi_prev_char + vi_next_word + vi_prev_word + vi_to_next_char + vi_to_prev_char + vi_end_word + vi_next_big_word + vi_prev_big_word + vi_end_big_word + vi_repeat_next_char + vi_repeat_prev_char + } + + module CompletionState + NORMAL = :normal + COMPLETION = :completion + MENU = :menu + JOURNEY = :journey + PERFECT_MATCH = :perfect_match + end + + CompletionJourneyData = Struct.new('CompletionJourneyData', :preposing, :postposing, :list, :pointer) + MenuInfo = Struct.new('MenuInfo', :target, :list) + + def initialize(config, prompt, encoding = Encoding.default_external) + @config = config + @prompt = prompt + @prompt_width = calculate_width(@prompt) + @cursor = 0 + @cursor_max = 0 + @byte_pointer = 0 + @encoding = encoding + @buffer_of_lines = [String.new(encoding: @encoding)] + @line_index = 0 + @previous_line_index = nil + @line = @buffer_of_lines[0] + @is_multiline = false + @finished = false + @cleared = false + @rerender_all = false + @is_confirm_multiline_termination = false + @history_pointer = nil + @line_backup_in_history = nil + @kill_ring = Reline::KillRing.new + @vi_clipboard = '' + @vi_arg = nil + @multibyte_buffer = String.new(encoding: 'ASCII-8BIT') + @meta_prefix = false + @waiting_proc = nil + @waiting_operator_proc = nil + @completion_journey_data = nil + @completion_state = CompletionState::NORMAL + @perfect_matched = nil + @first_line_started_from = 0 + @move_up = 0 + @started_from = 0 + @highest_in_this = 1 + @highest_in_all = 1 + @menu_info = nil + end + + def multiline_on + @is_multiline = true + end + + def multiline_off + @is_multiline = false + end + + private def insert_new_line(cursor_line, next_line) + @line = cursor_line + @buffer_of_lines.insert(@line_index + 1, String.new(next_line, encoding: @encoding)) + @previous_line_index = @line_index + @line_index += 1 + end + + private def calculate_height_by_width(width) + return 1 if width.zero? + height = 1 + max_width = @screen_size.last + while width > max_width * height + height += 1 + end + height += 1 if (width % max_width).zero? + height + end + + private def split_by_width(str, max_width) + lines = [String.new(encoding: @encoding)] + width = 0 + str.encode(Encoding::UTF_8).grapheme_clusters.each do |gc| + mbchar_width = Reline::Unicode.get_mbchar_width(gc) + width += mbchar_width + if width > max_width + width = mbchar_width + lines << String.new(encoding: @encoding) + end + lines.last << gc + end + # The cursor moves to next line in first + lines << String.new(encoding: @encoding) if width == max_width + lines + end + + private def scroll_down(val) + if val <= @rest_height + Reline.move_cursor_down(val) + @rest_height -= val + else + Reline.move_cursor_down(@rest_height) + Reline.scroll_down(val - @rest_height) + @rest_height = 0 + end + end + + private def move_cursor_up(val) + if val > 0 + Reline.move_cursor_up(val) + @rest_height += val + elsif val < 0 + move_cursor_down(-val) + end + end + + private def move_cursor_down(val) + if val > 0 + Reline.move_cursor_down(val) + @rest_height -= val + @rest_height = 0 if @rest_height < 0 + elsif val < 0 + move_cursor_up(-val) + end + end + + private def calculate_nearest_cursor + @cursor_max = calculate_width(line) + new_cursor = 0 + new_byte_pointer = 0 + height = 1 + max_width = @screen_size.last + @line.encode(Encoding::UTF_8).grapheme_clusters.each do |gc| + mbchar_width = Reline::Unicode.get_mbchar_width(gc) + now = new_cursor + mbchar_width + if now > @cursor_max or now > @cursor + break + end + new_cursor += mbchar_width + if new_cursor > max_width * height + height += 1 + end + new_byte_pointer += gc.bytesize + end + @started_from = height - 1 + @cursor = new_cursor + @byte_pointer = new_byte_pointer + end + + def rerender # TODO: support physical and logical lines + @rest_height ||= (Reline.get_screen_size.first - 1) - Reline.cursor_pos.y + @screen_size ||= Reline.get_screen_size + if @menu_info + puts + @menu_info.list.each do |item| + puts item + end + @menu_info = nil + end + return if @line.nil? + if @vi_arg + prompt = "(arg: #{@vi_arg}) " + prompt_width = calculate_width(prompt) + else + prompt = @prompt + prompt_width = @prompt_width + end + if @cleared + Reline.clear_screen + @cleared = false + back = 0 + @buffer_of_lines.each_with_index do |line, index| + line = @line if index == @line_index + height = render_partial(prompt, prompt_width, line, false) + if index < (@buffer_of_lines.size - 1) + move_cursor_down(height) + back += height + end + end + move_cursor_up(back) + move_cursor_down(@first_line_started_from + @started_from) + Reline.move_cursor_column((prompt_width + @cursor) % @screen_size.last) + return + end + # FIXME: end of logical line sometimes breaks + if @previous_line_index + previous_line = @line + all_height = @buffer_of_lines.inject(0) { |result, line| + result + calculate_height_by_width(@prompt_width + calculate_width(line)) + } + diff = all_height - @highest_in_all + if diff > 0 + @highest_in_all = all_height + scroll_down(diff) + move_cursor_up(@first_line_started_from + @started_from + diff) + back = 0 + @buffer_of_lines.each_with_index do |line, index| + line = @line if index == @previous_line_index + height = render_partial(prompt, prompt_width, line, false) + if index < (@buffer_of_lines.size - 1) + move_cursor_down(height) + back += height + end + end + move_cursor_up(back) + else + render_partial(prompt, prompt_width, previous_line) + move_cursor_up(@first_line_started_from + @started_from) + end + @buffer_of_lines[@previous_line_index] = @line + @line = @buffer_of_lines[@line_index] + @first_line_started_from = + if @line_index.zero? + 0 + else + @buffer_of_lines[0..(@line_index - 1)].inject(0) { |result, line| + result + calculate_height_by_width(@prompt_width + calculate_width(line)) + } + end + move_cursor_down(@first_line_started_from) + calculate_nearest_cursor + @highest_in_this = calculate_height_by_width(@prompt_width + @cursor_max) + @previous_line_index = nil + elsif @rerender_all + move_cursor_up(@first_line_started_from + @started_from) + Reline.move_cursor_column(0) + back = 0 + @buffer_of_lines.each do |line| + width = prompt_width + calculate_width(line) + height = calculate_height_by_width(width) + back += height + end + if back > @highest_in_all + scroll_down(back) + move_cursor_up(back) + elsif back < @highest_in_all + scroll_down(back) + Reline.erase_after_cursor + (@highest_in_all - back).times do + scroll_down(1) + Reline.erase_after_cursor + end + move_cursor_up(@highest_in_all) + end + @buffer_of_lines.each_with_index do |line, index| + render_partial(prompt, prompt_width, line, false) + if index < (@buffer_of_lines.size - 1) + move_cursor_down(1) + end + end + move_cursor_up(back - 1) + @highest_in_all = back + @highest_in_this = calculate_height_by_width(@prompt_width + @cursor_max) + @first_line_started_from = + if @line_index.zero? + 0 + else + @buffer_of_lines[0..(@line_index - 1)].inject(0) { |result, line| + result + calculate_height_by_width(@prompt_width + calculate_width(line)) + } + end + move_cursor_down(@first_line_started_from) + @rerender_all = false + end + render_partial(prompt, prompt_width, @line) if !@is_multiline or !finished? + if @is_multiline and finished? + scroll_down(1) unless @buffer_of_lines.last.empty? + Reline.move_cursor_column(0) + Reline.erase_after_cursor + end + end + + private def render_partial(prompt, prompt_width, line_to_render, with_control = true) + whole_line = prompt + (line_to_render.nil? ? '' : line_to_render) + visual_lines = split_by_width(whole_line, @screen_size.last) + if with_control + if visual_lines.size > @highest_in_this + diff = visual_lines.size - @highest_in_this + scroll_down(diff) + @highest_in_all += diff + @highest_in_this = visual_lines.size + move_cursor_up(1) + end + move_cursor_up(@started_from) + @started_from = calculate_height_by_width(prompt_width + @cursor) - 1 + end + visual_lines.each_with_index do |line, index| + Reline.move_cursor_column(0) + escaped_print line + Reline.erase_after_cursor + move_cursor_down(1) if index < (visual_lines.size - 1) + end + if with_control + if finished? + puts + else + move_cursor_up((visual_lines.size - 1) - @started_from) + Reline.move_cursor_column((prompt_width + @cursor) % @screen_size.last) + end + end + visual_lines.size + end + + def editing_mode + @config.editing_mode + end + + private def escaped_print(str) + print str.chars.map { |gr| + escaped = Reline::Unicode::EscapedPairs[gr.ord] + if escaped + escaped + else + gr + end + }.join + end + + private def menu(target, list) + @menu_info = MenuInfo.new(target, list) + end + + private def complete_internal_proc(list, is_menu) + preposing, target, postposing = @retrieve_completion_block.(@line, @byte_pointer) + list = list.select { |i| i&.start_with?(target) } + if is_menu + menu(target, list) + return nil + end + completed = list.inject { |memo, item| + memo_mbchars = memo.unicode_normalize.grapheme_clusters + item_mbchars = item.unicode_normalize.grapheme_clusters + size = [memo_mbchars.size, item_mbchars.size].min + result = '' + size.times do |i| + if memo_mbchars[i] == item_mbchars[i] + result << memo_mbchars[i] + else + break + end + end + result + } + [target, preposing, completed, postposing] + end + + private def complete(list) + case @completion_state + when CompletionState::NORMAL, CompletionState::JOURNEY + @completion_state = CompletionState::COMPLETION + when CompletionState::PERFECT_MATCH + @dig_perfect_match_proc&.(@perfect_matched) + end + is_menu = (@completion_state == CompletionState::MENU) + result = complete_internal_proc(list, is_menu) + return if result.nil? + target, preposing, completed, postposing = result + return if completed.nil? + if target <= completed and (@completion_state == CompletionState::COMPLETION or @completion_state == CompletionState::PERFECT_MATCH) + @completion_state = CompletionState::MENU + if list.include?(completed) + @completion_state = CompletionState::PERFECT_MATCH + @perfect_matched = completed + end + if target < completed + @line = preposing + completed + postposing + line_to_pointer = preposing + completed + @cursor_max = calculate_width(@line) + @cursor = calculate_width(line_to_pointer) + @byte_pointer = line_to_pointer.bytesize + end + end + end + + private def move_completed_list(list, direction) + case @completion_state + when CompletionState::NORMAL, CompletionState::COMPLETION, CompletionState::MENU + @completion_state = CompletionState::JOURNEY + result = @retrieve_completion_block.(@line, @byte_pointer) + return if result.nil? + preposing, target, postposing = result + @completion_journey_data = CompletionJourneyData.new( + preposing, postposing, + [target] + list.select{ |item| item.start_with?(target) }, 0) + @completion_state = CompletionState::JOURNEY + else + case direction + when :up + @completion_journey_data.pointer -= 1 + if @completion_journey_data.pointer < 0 + @completion_journey_data.pointer = @completion_journey_data.list.size - 1 + end + when :down + @completion_journey_data.pointer += 1 + if @completion_journey_data.pointer >= @completion_journey_data.list.size + @completion_journey_data.pointer = 0 + end + end + completed = @completion_journey_data.list[@completion_journey_data.pointer] + @line = @completion_journey_data.preposing + completed + @completion_journey_data.postposing + line_to_pointer = @completion_journey_data.preposing + completed + @cursor_max = calculate_width(@line) + @cursor = calculate_width(line_to_pointer) + @byte_pointer = line_to_pointer.bytesize + end + end + + private def run_for_operators(key, method_symbol, &block) + if @waiting_operator_proc + if VI_MOTIONS.include?(method_symbol) + old_cursor, old_byte_pointer = @cursor, @byte_pointer + block.() + unless @waiting_proc + cursor_diff, byte_pointer_diff = @cursor - old_cursor, @byte_pointer - old_byte_pointer + @cursor, @byte_pointer = old_cursor, old_byte_pointer + @waiting_operator_proc.(cursor_diff, byte_pointer_diff) + else + old_waiting_proc = @waiting_proc + old_waiting_operator_proc = @waiting_operator_proc + @waiting_proc = proc { |key| + old_cursor, old_byte_pointer = @cursor, @byte_pointer + old_waiting_proc.(key) + cursor_diff, byte_pointer_diff = @cursor - old_cursor, @byte_pointer - old_byte_pointer + @cursor, @byte_pointer = old_cursor, old_byte_pointer + @waiting_operator_proc.(cursor_diff, byte_pointer_diff) + @waiting_operator_proc = old_waiting_operator_proc + } + end + else + # Ignores operator when not motion is given. + block.() + end + @waiting_operator_proc = nil + else + block.() + end + end + + private def process_key(key, method_symbol, method_obj) + if @vi_arg + if key.chr =~ /[0-9]/ + ed_argument_digit(key) + else + if ARGUMENTABLE.include?(method_symbol) and method_obj + run_for_operators(key, method_symbol) do + method_obj.(key, arg: @vi_arg) + end + elsif @waiting_proc + @waiting_proc.(key) + elsif method_obj + method_obj.(key) + else + ed_insert(key) + end + @kill_ring.process + @vi_arg = nil + end + elsif @waiting_proc + @waiting_proc.(key) + @kill_ring.process + elsif method_obj + if method_symbol == :ed_argument_digit + method_obj.(key) + else + run_for_operators(key, method_symbol) do + method_obj.(key) + end + end + @kill_ring.process + else + ed_insert(key) + end + end + + private def normal_char(key) + method_symbol = method_obj = nil + @multibyte_buffer << key + if @multibyte_buffer.size > 1 + if @multibyte_buffer.dup.force_encoding(@encoding).valid_encoding? + key = @multibyte_buffer.dup.force_encoding(@encoding) + @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) + 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 + mbchar = @line.byteslice(@byte_pointer, byte_size) + width = Reline::Unicode.get_mbchar_width(mbchar) + @cursor -= width + end + end + + def input_key(key) + completion_occurs = false + if @config.editing_mode_is?(:emacs, :vi_insert) and key == "\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) + result = @completion_proc&.(@line) + if result.is_a?(Array) + completion_occurs = true + move_completed_list(result, "\C-p".ord == key ? :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)) + else + normal_char(key) + end + unless completion_occurs + @completion_state = CompletionState::NORMAL + end + if @is_confirm_multiline_termination and @confirm_multiline_termination_proc + @is_confirm_multiline_termination = false + temp_buffer = @buffer_of_lines.dup + if @previous_line_index and @line_index == (@buffer_of_lines.size - 1) + temp_buffer[@previous_line_index] = @line + end + finish if @confirm_multiline_termination_proc.(temp_buffer.join("\n")) + end + end + + def whole_buffer + temp_lines = @buffer_of_lines.dup + temp_lines[@line_index] = @line + if @buffer_of_lines.size == 1 and @line.nil? + nil + else + temp_lines.join("\n") + end + end + + def finished? + @finished + end + + def finish + @finished = true + @config.reset + end + + private def byteslice!(str, byte_pointer, size) + new_str = str.byteslice(0, byte_pointer) + new_str << str.byteslice(byte_pointer + size, str.bytesize) + [new_str, str.byteslice(byte_pointer, size)] + end + + private def byteinsert(str, byte_pointer, other) + new_str = str.byteslice(0, byte_pointer) + new_str << other + new_str << str.byteslice(byte_pointer, str.bytesize) + new_str + end + + private def calculate_width(str) + str.encode(Encoding::UTF_8).grapheme_clusters.inject(0) { |width, gc| + width + Reline::Unicode.get_mbchar_width(gc) + } + end + + private def ed_insert(key) + if key.instance_of?(String) + width = Reline::Unicode.get_mbchar_width(key) + if @cursor == @cursor_max + @line += key + else + @line = byteinsert(@line, @byte_pointer, key) + end + @byte_pointer += key.bytesize + @cursor += width + @cursor_max += width + else + if @cursor == @cursor_max + @line += key.chr + else + @line = byteinsert(@line, @byte_pointer, key.chr) + end + width = Reline::Unicode.get_mbchar_width(key.chr) + @byte_pointer += 1 + @cursor += width + @cursor_max += width + end + end + alias_method :ed_digit, :ed_insert + + private def ed_quoted_insert(str, arg: 1) + @waiting_proc = proc { |key| + arg.times do + ed_insert(key) + end + @waiting_proc = nil + } + end + + private def ed_next_char(key, arg: 1) + byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer) + if (@byte_pointer < @line.bytesize) + mbchar = @line.byteslice(@byte_pointer, byte_size) + width = Reline::Unicode.get_mbchar_width(mbchar) + @cursor += width if width + @byte_pointer += byte_size + end + arg -= 1 + ed_next_char(key, arg: arg) if arg > 0 + end + + private def ed_prev_char(key, arg: 1) + if @cursor > 0 + byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer) + @byte_pointer -= byte_size + mbchar = @line.byteslice(@byte_pointer, byte_size) + width = Reline::Unicode.get_mbchar_width(mbchar) + @cursor -= width + end + arg -= 1 + ed_prev_char(key, arg: arg) if arg > 0 + end + + private def ed_move_to_beg(key) + @byte_pointer, @cursor = Reline::Unicode.ed_move_to_begin(@line) + end + + private def ed_move_to_end(key) + @byte_pointer = 0 + @cursor = 0 + byte_size = 0 + while @byte_pointer < @line.bytesize + byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer) + if byte_size > 0 + mbchar = @line.byteslice(@byte_pointer, byte_size) + @cursor += Reline::Unicode.get_mbchar_width(mbchar) + end + @byte_pointer += byte_size + end + end + + private def ed_prev_history(key, arg: 1) + if @is_multiline and @line_index > 0 + @previous_line_index = @line_index + @line_index -= 1 + return + end + if Reline::HISTORY.empty? + return + end + if @history_pointer.nil? + @history_pointer = Reline::HISTORY.size - 1 + if @is_multiline + @line_backup_in_history = whole_buffer + @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n") + @line_index = @buffer_of_lines.size - 1 + @line = @buffer_of_lines.last + @rerender_all = true + else + @line_backup_in_history = @line + @line = Reline::HISTORY[@history_pointer] + end + elsif @history_pointer.zero? + return + else + if @is_multiline + Reline::HISTORY[@history_pointer] = whole_buffer + @history_pointer -= 1 + @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n") + @line_index = @buffer_of_lines.size - 1 + @line = @buffer_of_lines.last + @rerender_all = true + else + Reline::HISTORY[@history_pointer] = @line + @history_pointer -= 1 + @line = Reline::HISTORY[@history_pointer] + end + end + if @config.editing_mode_is?(:emacs) + @cursor_max = @cursor = calculate_width(@line) + @byte_pointer = @line.bytesize + elsif @config.editing_mode_is?(:vi_command) + @byte_pointer = @cursor = 0 + @cursor_max = calculate_width(@line) + end + arg -= 1 + ed_prev_history(key, arg: arg) if arg > 0 + end + + private def ed_next_history(key, arg: 1) + if @is_multiline and @line_index < (@buffer_of_lines.size - 1) + @previous_line_index = @line_index + @line_index += 1 + return + end + if @history_pointer.nil? + return + elsif @history_pointer == (Reline::HISTORY.size - 1) + if @is_multiline + @history_pointer = nil + @buffer_of_lines = @line_backup_in_history.split("\n") + @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty? + @line_index = 0 + @line = @buffer_of_lines.first + @rerender_all = true + else + @history_pointer = nil + @line = @line_backup_in_history + end + else + if @is_multiline + Reline::HISTORY[@history_pointer] = whole_buffer + @history_pointer += 1 + @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n") + @line_index = 0 + @line = @buffer_of_lines.first + @rerender_all = true + else + Reline::HISTORY[@history_pointer] = @line + @history_pointer += 1 + @line = Reline::HISTORY[@history_pointer] + end + end + @line = '' unless @line + if @config.editing_mode_is?(:emacs) + @cursor_max = @cursor = calculate_width(@line) + @byte_pointer = @line.bytesize + elsif @config.editing_mode_is?(:vi_command) + @byte_pointer = @cursor = 0 + @cursor_max = calculate_width(@line) + end + arg -= 1 + ed_next_history(key, arg: arg) if arg > 0 + end + + private def ed_newline(key) + if @is_multiline + if @config.editing_mode_is?(:vi_command) + if @line_index < (@buffer_of_lines.size - 1) + ed_next_history(key) + else + @is_confirm_multiline_termination = true + end + else + next_line = @line.byteslice(@byte_pointer, @line.bytesize - @byte_pointer) + cursor_line = @line.byteslice(0, @byte_pointer) + insert_new_line(cursor_line, next_line) + if @line_index == (@buffer_of_lines.size - 1) + @is_confirm_multiline_termination = true + end + end + return + end + if @history_pointer + Reline::HISTORY[@history_pointer] = @line + @history_pointer = nil + end + finish + end + + private def em_delete_prev_char(key) + if @is_multiline and @cursor == 0 and @line_index > 0 + @buffer_of_lines[@line_index] = @line + @cursor = calculate_width(@buffer_of_lines[@line_index - 1]) + @byte_pointer = @buffer_of_lines[@line_index - 1].bytesize + @buffer_of_lines[@line_index - 1] += @buffer_of_lines.delete_at(@line_index) + @line_index -= 1 + @line = @buffer_of_lines[@line_index] + @cursor_max = calculate_width(@line) + @rerender_all = true + elsif @cursor > 0 + byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer) + @byte_pointer -= byte_size + @line, mbchar = byteslice!(@line, @byte_pointer, byte_size) + width = Reline::Unicode.get_mbchar_width(mbchar) + @cursor -= width + @cursor_max -= width + end + end + + private def ed_kill_line(key) + if @line.bytesize > @byte_pointer + @line, deleted = byteslice!(@line, @byte_pointer, @line.bytesize - @byte_pointer) + @byte_pointer = @line.bytesize + @cursor = @cursor_max = calculate_width(@line) + @kill_ring.append(deleted) + end + end + + private def em_kill_line(key) + if @byte_pointer > 0 + @line, deleted = byteslice!(@line, 0, @byte_pointer) + @byte_pointer = 0 + @kill_ring.append(deleted, true) + @cursor_max = calculate_width(@line) + @cursor = 0 + end + end + + private def em_delete_or_list(key) + if @line.empty? + @line = nil + finish + elsif @byte_pointer < @line.bytesize + splitted_last = @line.byteslice(@byte_pointer, @line.bytesize) + mbchar = splitted_last.grapheme_clusters.first + width = Reline::Unicode.get_mbchar_width(mbchar) + @cursor_max -= width + @line, = byteslice!(@line, @byte_pointer, mbchar.bytesize) + end + end + + private def em_yank(key) + yanked = @kill_ring.yank + if yanked + @line = byteinsert(@line, @byte_pointer, yanked) + yanked_width = calculate_width(yanked) + @cursor += yanked_width + @cursor_max += yanked_width + @byte_pointer += yanked.bytesize + end + end + + private def em_yank_pop(key) + yanked, prev_yank = @kill_ring.yank_pop + if yanked + prev_yank_width = calculate_width(prev_yank) + @cursor -= prev_yank_width + @cursor_max -= prev_yank_width + @byte_pointer -= prev_yank.bytesize + @line, = byteslice!(@line, @byte_pointer, prev_yank.bytesize) + @line = byteinsert(@line, @byte_pointer, yanked) + yanked_width = calculate_width(yanked) + @cursor += yanked_width + @cursor_max += yanked_width + @byte_pointer += yanked.bytesize + end + end + + private def ed_clear_screen(key) + @cleared = true + end + + private def em_next_word(key) + if @line.bytesize > @byte_pointer + byte_size, width = Reline::Unicode.em_forward_word(@line, @byte_pointer) + @byte_pointer += byte_size + @cursor += width + end + end + + private def ed_prev_word(key) + if @byte_pointer > 0 + byte_size, width = Reline::Unicode.em_backward_word(@line, @byte_pointer) + @byte_pointer -= byte_size + @cursor -= width + end + end + + private def em_delete_next_word(key) + if @line.bytesize > @byte_pointer + byte_size, width = Reline::Unicode.em_forward_word(@line, @byte_pointer) + @line, word = byteslice!(@line, @byte_pointer, byte_size) + @kill_ring.append(word) + @cursor_max -= width + end + end + + private def ed_delete_prev_word(key) + if @byte_pointer > 0 + byte_size, width = Reline::Unicode.em_backward_word(@line, @byte_pointer) + @line, word = byteslice!(@line, @byte_pointer - byte_size, byte_size) + @kill_ring.append(word, true) + @byte_pointer -= byte_size + @cursor -= width + @cursor_max -= width + end + end + + private def ed_transpose_chars(key) + if @byte_pointer > 0 + if @cursor_max > @cursor + byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer) + mbchar = @line.byteslice(@byte_pointer, byte_size) + width = Reline::Unicode.get_mbchar_width(mbchar) + @cursor += width + @byte_pointer += byte_size + end + back1_byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer) + if (@byte_pointer - back1_byte_size) > 0 + back2_byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer - back1_byte_size) + back2_pointer = @byte_pointer - back1_byte_size - back2_byte_size + @line, back2_mbchar = byteslice!(@line, back2_pointer, back2_byte_size) + @line = byteinsert(@line, @byte_pointer - back2_byte_size, back2_mbchar) + end + end + end + + private def em_capitol_case(key) + if @line.bytesize > @byte_pointer + byte_size, _, new_str = Reline::Unicode.em_forward_word_with_capitalization(@line, @byte_pointer) + before = @line.byteslice(0, @byte_pointer) + after = @line.byteslice((@byte_pointer + byte_size)..-1) + @line = before + new_str + after + @byte_pointer += new_str.bytesize + @cursor += calculate_width(new_str) + end + end + + private def em_lower_case(key) + if @line.bytesize > @byte_pointer + byte_size, = Reline::Unicode.em_forward_word(@line, @byte_pointer) + part = @line.byteslice(@byte_pointer, byte_size).grapheme_clusters.map { |mbchar| + mbchar =~ /[A-Z]/ ? mbchar.downcase : mbchar + }.join + rest = @line.byteslice((@byte_pointer + byte_size)..-1) + @line = @line.byteslice(0, @byte_pointer) + part + @byte_pointer = @line.bytesize + @cursor = calculate_width(@line) + @cursor_max = @cursor + calculate_width(rest) + @line += rest + end + end + + private def em_upper_case(key) + if @line.bytesize > @byte_pointer + byte_size, = Reline::Unicode.em_forward_word(@line, @byte_pointer) + part = @line.byteslice(@byte_pointer, byte_size).grapheme_clusters.map { |mbchar| + mbchar =~ /[a-z]/ ? mbchar.upcase : mbchar + }.join + rest = @line.byteslice((@byte_pointer + byte_size)..-1) + @line = @line.byteslice(0, @byte_pointer) + part + @byte_pointer = @line.bytesize + @cursor = calculate_width(@line) + @cursor_max = @cursor + calculate_width(rest) + @line += rest + end + end + + private def em_kill_region(key) + if @byte_pointer > 0 + byte_size, width = Reline::Unicode.em_big_backward_word(@line, @byte_pointer) + @line, deleted = byteslice!(@line, @byte_pointer - byte_size, byte_size) + @byte_pointer -= byte_size + @cursor -= width + @cursor_max -= width + @kill_ring.append(deleted) + end + end + + private def copy_for_vi(text) + if @config.editing_mode_is?(:vi_insert) or @config.editing_mode_is?(:vi_command) + @vi_clipboard = text + end + end + + private def vi_insert(key) + @config.editing_mode = :vi_insert + end + + private def vi_add(key) + @config.editing_mode = :vi_insert + ed_next_char(key) + end + + private def vi_command_mode(key) + ed_prev_char(key) + @config.editing_mode = :vi_command + end + + private def vi_next_word(key, arg: 1) + if @line.bytesize > @byte_pointer + byte_size, width = Reline::Unicode.vi_forward_word(@line, @byte_pointer) + @byte_pointer += byte_size + @cursor += width + end + arg -= 1 + vi_next_word(key, arg: arg) if arg > 0 + end + + private def vi_prev_word(key, arg: 1) + if @byte_pointer > 0 + byte_size, width = Reline::Unicode.vi_backward_word(@line, @byte_pointer) + @byte_pointer -= byte_size + @cursor -= width + end + arg -= 1 + vi_prev_word(key, arg: arg) if arg > 0 + end + + private def vi_end_word(key, arg: 1) + if @line.bytesize > @byte_pointer + byte_size, width = Reline::Unicode.vi_forward_end_word(@line, @byte_pointer) + @byte_pointer += byte_size + @cursor += width + end + arg -= 1 + vi_end_word(key, arg: arg) if arg > 0 + end + + private def vi_next_big_word(key, arg: 1) + if @line.bytesize > @byte_pointer + byte_size, width = Reline::Unicode.vi_big_forward_word(@line, @byte_pointer) + @byte_pointer += byte_size + @cursor += width + end + arg -= 1 + vi_next_big_word(key, arg: arg) if arg > 0 + end + + private def vi_prev_big_word(key, arg: 1) + if @byte_pointer > 0 + byte_size, width = Reline::Unicode.vi_big_backward_word(@line, @byte_pointer) + @byte_pointer -= byte_size + @cursor -= width + end + arg -= 1 + vi_prev_big_word(key, arg: arg) if arg > 0 + end + + private def vi_end_big_word(key, arg: 1) + if @line.bytesize > @byte_pointer + byte_size, width = Reline::Unicode.vi_big_forward_end_word(@line, @byte_pointer) + @byte_pointer += byte_size + @cursor += width + end + arg -= 1 + vi_end_big_word(key, arg: arg) if arg > 0 + end + + private def vi_delete_prev_char(key) + if @is_multiline and @cursor == 0 and @line_index > 0 + @buffer_of_lines[@line_index] = @line + @cursor = calculate_width(@buffer_of_lines[@line_index - 1]) + @byte_pointer = @buffer_of_lines[@line_index - 1].bytesize + @buffer_of_lines[@line_index - 1] += @buffer_of_lines.delete_at(@line_index) + @line_index -= 1 + @line = @buffer_of_lines[@line_index] + @cursor_max = calculate_width(@line) + @rerender_all = true + elsif @cursor > 0 + byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer) + @byte_pointer -= byte_size + @line, mbchar = byteslice!(@line, @byte_pointer, byte_size) + width = Reline::Unicode.get_mbchar_width(mbchar) + @cursor -= width + @cursor_max -= width + end + end + + private def ed_delete_prev_char(key, arg: 1) + deleted = '' + arg.times do + if @cursor > 0 + byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer) + @byte_pointer -= byte_size + @line, mbchar = byteslice!(@line, @byte_pointer, byte_size) + deleted.prepend(mbchar) + width = Reline::Unicode.get_mbchar_width(mbchar) + @cursor -= width + @cursor_max -= width + end + end + copy_for_vi(deleted) + end + + private def vi_zero(key) + @byte_pointer = 0 + @cursor = 0 + end + + private def vi_change_meta(key) + end + + private def vi_delete_meta(key) + @waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff| + if byte_pointer_diff > 0 + @line, cut = byteslice!(@line, @byte_pointer, byte_pointer_diff) + elsif byte_pointer_diff < 0 + @line, cut = byteslice!(@line, @byte_pointer + byte_pointer_diff, -byte_pointer_diff) + end + copy_for_vi(cut) + @cursor += cursor_diff if cursor_diff < 0 + @cursor_max -= cursor_diff.abs + @byte_pointer += byte_pointer_diff if byte_pointer_diff < 0 + } + end + + private def vi_yank(key) + end + + private def vi_end_of_transmission(key) + if @line.empty? + @line = nil + finish + end + end + + private def vi_list_or_eof(key) + if @line.empty? + @line = nil + finish + else + # TODO: list + end + end + + private def ed_delete_next_char(key, arg: 1) + unless @line.empty? + byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer) + @line, mbchar = byteslice!(@line, @byte_pointer, byte_size) + copy_for_vi(mbchar) + width = Reline::Unicode.get_mbchar_width(mbchar) + @cursor_max -= width + if @cursor > 0 and @cursor >= @cursor_max + @byte_pointer -= byte_size + @cursor -= width + end + end + arg -= 1 + ed_delete_next_char(key, arg: arg) if arg > 0 + end + + private def vi_to_history_line(key) + if Reline::HISTORY.empty? + return + end + if @history_pointer.nil? + @history_pointer = 0 + @line_backup_in_history = @line + @line = Reline::HISTORY[@history_pointer] + @cursor_max = calculate_width(@line) + @cursor = 0 + @byte_pointer = 0 + elsif @history_pointer.zero? + return + else + Reline::HISTORY[@history_pointer] = @line + @history_pointer = 0 + @line = Reline::HISTORY[@history_pointer] + @cursor_max = calculate_width(@line) + @cursor = 0 + @byte_pointer = 0 + end + end + + private def vi_histedit(key) + path = Tempfile.open { |fp| + fp.write @line + fp.path + } + system("#{ENV['EDITOR']} #{path}") + @line = Pathname.new(path).read + finish + end + + private def vi_paste_prev(key, arg: 1) + if @vi_clipboard.size > 0 + @line = byteinsert(@line, @byte_pointer, @vi_clipboard) + @cursor_max += calculate_width(@vi_clipboard) + cursor_point = @vi_clipboard.grapheme_clusters[0..-2].join + @cursor += calculate_width(cursor_point) + @byte_pointer += cursor_point.bytesize + end + arg -= 1 + vi_paste_prev(key, arg: arg) if arg > 0 + end + + private def vi_paste_next(key, arg: 1) + if @vi_clipboard.size > 0 + byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer) + @line = byteinsert(@line, @byte_pointer + byte_size, @vi_clipboard) + @cursor_max += calculate_width(@vi_clipboard) + @cursor += calculate_width(@vi_clipboard) + @byte_pointer += @vi_clipboard.bytesize + end + arg -= 1 + vi_paste_next(key, arg: arg) if arg > 0 + end + + private def ed_argument_digit(key) + if @vi_arg.nil? + unless key.chr.to_i.zero? + @vi_arg = key.chr.to_i + end + else + @vi_arg = @vi_arg * 10 + key.chr.to_i + end + end + + private def vi_to_column(key, arg: 0) + @byte_pointer, @cursor = @line.grapheme_clusters.inject([0, 0]) { |total, gc| + # total has [byte_size, cursor] + mbchar_width = Reline::Unicode.get_mbchar_width(gc) + if (total.last + mbchar_width) >= arg + break total + elsif (total.last + mbchar_width) >= @cursor_max + break total + else + total = [total.first + gc.bytesize, total.last + mbchar_width] + total + end + } + end + + private def vi_replace_char(key, arg: 1) + @waiting_proc = ->(key) { + if arg == 1 + byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer) + before = @line.byteslice(0, @byte_pointer) + remaining_point = @byte_pointer + byte_size + after = @line.byteslice(remaining_point, @line.size - remaining_point) + @line = before + key.chr + after + @cursor_max = calculate_width(@line) + @waiting_proc = nil + elsif arg > 1 + byte_size = 0 + arg.times do + byte_size += Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer + byte_size) + end + before = @line.byteslice(0, @byte_pointer) + remaining_point = @byte_pointer + byte_size + after = @line.byteslice(remaining_point, @line.size - remaining_point) + replaced = key.chr * arg + @line = before + replaced + after + @byte_pointer += replaced.bytesize + @cursor += calculate_width(replaced) + @cursor_max = calculate_width(@line) + @waiting_proc = nil + end + } + end + + private def vi_next_char(key, arg: 1) + @waiting_proc = ->(key_for_proc) { search_next_char(key_for_proc, arg) } + end + + private def search_next_char(key, arg) + if key.instance_of?(String) + inputed_char = key + else + inputed_char = key.chr + end + total = nil + found = false + @line.byteslice(@byte_pointer..-1).grapheme_clusters.each do |mbchar| + # total has [byte_size, cursor] + unless total + # skip cursor point + width = Reline::Unicode.get_mbchar_width(mbchar) + total = [mbchar.bytesize, width] + else + if inputed_char == mbchar + arg -= 1 + if arg.zero? + found = true + break + end + end + width = Reline::Unicode.get_mbchar_width(mbchar) + total = [total.first + mbchar.bytesize, total.last + width] + end + end + if found and total + byte_size, width = total + @byte_pointer += byte_size + @cursor += width + end + @waiting_proc = nil + end +end diff --git a/lib/reline/reline.gemspec b/lib/reline/reline.gemspec new file mode 100644 index 00000000000000..b8e96d6613dc63 --- /dev/null +++ b/lib/reline/reline.gemspec @@ -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 diff --git a/lib/reline/unicode.rb b/lib/reline/unicode.rb new file mode 100644 index 00000000000000..bdb182f59c3c16 --- /dev/null +++ b/lib/reline/unicode.rb @@ -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' diff --git a/lib/reline/unicode/east_asian_width.rb b/lib/reline/unicode/east_asian_width.rb new file mode 100644 index 00000000000000..4eea3a7cdf85f4 --- /dev/null +++ b/lib/reline/unicode/east_asian_width.rb @@ -0,0 +1,1145 @@ +class Reline::Unicode::EastAsianWidth + # This is based on EastAsianWidth.txt + # http://www.unicode.org/Public/10.0.0/ucd/EastAsianWidth.txt + + # Fullwidth + TYPE_F = /^( + \u{3000} | + [\u{FF01}-\u{FF60}] | + [\u{FFE0}-\u{FFE6}] + )/x + + # Halfwidth + TYPE_H = /^( + \u{20A9} | + [\u{FF61}-\u{FFBE}] | + [\u{FFC2}-\u{FFC7}] | + [\u{FFCA}-\u{FFCF}] | + [\u{FFD2}-\u{FFD7}] | + [\u{FFDA}-\u{FFDC}] | + [\u{FFE8}-\u{FFEE}] + )/x + + # Wide + TYPE_W = /^( + [\u{1100}-\u{115F}] | + [\u{231A}-\u{231B}] | + [\u{2329}-\u{232A}] | + [\u{23E9}-\u{23EC}] | + \u{23F0} | + \u{23F3} | + [\u{25FD}-\u{25FE}] | + [\u{2614}-\u{2615}] | + [\u{2648}-\u{2653}] | + \u{267F} | + \u{2693} | + \u{26A1} | + [\u{26AA}-\u{26AB}] | + [\u{26BD}-\u{26BE}] | + [\u{26C4}-\u{26C5}] | + \u{26CE} | + \u{26D4} | + \u{26EA} | + [\u{26F2}-\u{26F3}] | + \u{26F5} | + \u{26FA} | + \u{26FD} | + \u{2705} | + [\u{270A}-\u{270B}] | + \u{2728} | + \u{274C} | + \u{274E} | + [\u{2753}-\u{2755}] | + \u{2757} | + [\u{2795}-\u{2797}] | + \u{27B0} | + \u{27BF} | + [\u{2B1B}-\u{2B1C}] | + \u{2B50} | + \u{2B55} | + [\u{2E80}-\u{2E99}] | + [\u{2E9B}-\u{2EF3}] | + [\u{2F00}-\u{2FD5}] | + [\u{2FF0}-\u{2FFB}] | + [\u{3001}-\u{303E}] | + [\u{3041}-\u{3096}] | + [\u{3099}-\u{30FF}] | + [\u{3105}-\u{312F}] | + [\u{3131}-\u{318E}] | + [\u{3190}-\u{31BA}] | + [\u{31C0}-\u{31E3}] | + [\u{31F0}-\u{321E}] | + [\u{3220}-\u{3247}] | + [\u{3250}-\u{4DBF}] | + [\u{4E00}-\u{A48C}] | + [\u{A490}-\u{A4C6}] | + [\u{A960}-\u{A97C}] | + [\u{AC00}-\u{D7A3}] | + [\u{F900}-\u{FAFF}] | + [\u{FE10}-\u{FE19}] | + [\u{FE30}-\u{FE52}] | + [\u{FE54}-\u{FE66}] | + [\u{FE68}-\u{FE6B}] | + [\u{16FE0}-\u{16FE3}] | + [\u{17000}-\u{187F7}] | + [\u{18800}-\u{18AF2}] | + [\u{1B000}-\u{1B11E}] | + [\u{1B150}-\u{1B152}] | + [\u{1B164}-\u{1B167}] | + [\u{1B170}-\u{1B2FB}] | + \u{1F004} | + \u{1F0CF} | + \u{1F18E} | + [\u{1F191}-\u{1F19A}] | + [\u{1F200}-\u{1F202}] | + [\u{1F210}-\u{1F23B}] | + [\u{1F240}-\u{1F248}] | + [\u{1F250}-\u{1F251}] | + [\u{1F260}-\u{1F265}] | + [\u{1F300}-\u{1F320}] | + [\u{1F32D}-\u{1F335}] | + [\u{1F337}-\u{1F37C}] | + [\u{1F37E}-\u{1F393}] | + [\u{1F3A0}-\u{1F3CA}] | + [\u{1F3CF}-\u{1F3D3}] | + [\u{1F3E0}-\u{1F3F0}] | + \u{1F3F4} | + [\u{1F3F8}-\u{1F43E}] | + \u{1F440} | + [\u{1F442}-\u{1F4FC}] | + [\u{1F4FF}-\u{1F53D}] | + [\u{1F54B}-\u{1F54E}] | + [\u{1F550}-\u{1F567}] | + \u{1F57A} | + [\u{1F595}-\u{1F596}] | + \u{1F5A4} | + [\u{1F5FB}-\u{1F64F}] | + [\u{1F680}-\u{1F6C5}] | + \u{1F6CC} | + [\u{1F6D0}-\u{1F6D2}] | + \u{1F6D5} | + [\u{1F6EB}-\u{1F6EC}] | + [\u{1F6F4}-\u{1F6FA}] | + [\u{1F7E0}-\u{1F7EB}] | + [\u{1F90D}-\u{1F971}] | + [\u{1F973}-\u{1F976}] | + [\u{1F97A}-\u{1F9A2}] | + [\u{1F9A5}-\u{1F9AA}] | + [\u{1F9AE}-\u{1F9CA}] | + [\u{1F9CD}-\u{1F9FF}] | + [\u{1FA70}-\u{1FA73}] | + [\u{1FA78}-\u{1FA7A}] | + [\u{1FA80}-\u{1FA82}] | + [\u{1FA90}-\u{1FA95}] | + [\u{20000}-\u{2FFFD}] | + [\u{30000}-\u{3FFFD}] + )/x + + # Narrow + TYPE_NA = /^( + [\u{0020}-\u{007E}] | + [\u{00A2}-\u{00A3}] | + [\u{00A5}-\u{00A6}] | + \u{00AC} | + \u{00AF} | + [\u{27E6}-\u{27ED}] | + [\u{2985}-\u{2986}] + )/x + + # Ambiguous + TYPE_A = /^( + \u{00A1} | + \u{00A4} | + [\u{00A7}-\u{00A8}] | + \u{00AA} | + [\u{00AD}-\u{00AE}] | + [\u{00B0}-\u{00B4}] | + [\u{00B6}-\u{00BA}] | + [\u{00BC}-\u{00BF}] | + \u{00C6} | + \u{00D0} | + [\u{00D7}-\u{00D8}] | + [\u{00DE}-\u{00E1}] | + \u{00E6} | + [\u{00E8}-\u{00EA}] | + [\u{00EC}-\u{00ED}] | + \u{00F0} | + [\u{00F2}-\u{00F3}] | + [\u{00F7}-\u{00FA}] | + \u{00FC} | + \u{00FE} | + \u{0101} | + \u{0111} | + \u{0113} | + \u{011B} | + [\u{0126}-\u{0127}] | + \u{012B} | + [\u{0131}-\u{0133}] | + \u{0138} | + [\u{013F}-\u{0142}] | + \u{0144} | + [\u{0148}-\u{014B}] | + \u{014D} | + [\u{0152}-\u{0153}] | + [\u{0166}-\u{0167}] | + \u{016B} | + \u{01CE} | + \u{01D0} | + \u{01D2} | + \u{01D4} | + \u{01D6} | + \u{01D8} | + \u{01DA} | + \u{01DC} | + \u{0251} | + \u{0261} | + \u{02C4} | + \u{02C7} | + [\u{02C9}-\u{02CB}] | + \u{02CD} | + \u{02D0} | + [\u{02D8}-\u{02DB}] | + \u{02DD} | + \u{02DF} | + [\u{0300}-\u{036F}] | + [\u{0391}-\u{03A1}] | + [\u{03A3}-\u{03A9}] | + [\u{03B1}-\u{03C1}] | + [\u{03C3}-\u{03C9}] | + \u{0401} | + [\u{0410}-\u{044F}] | + \u{0451} | + \u{2010} | + [\u{2013}-\u{2016}] | + [\u{2018}-\u{2019}] | + [\u{201C}-\u{201D}] | + [\u{2020}-\u{2022}] | + [\u{2024}-\u{2027}] | + \u{2030} | + [\u{2032}-\u{2033}] | + \u{2035} | + \u{203B} | + \u{203E} | + \u{2074} | + \u{207F} | + [\u{2081}-\u{2084}] | + \u{20AC} | + \u{2103} | + \u{2105} | + \u{2109} | + \u{2113} | + \u{2116} | + [\u{2121}-\u{2122}] | + \u{2126} | + \u{212B} | + [\u{2153}-\u{2154}] | + [\u{215B}-\u{215E}] | + [\u{2160}-\u{216B}] | + [\u{2170}-\u{2179}] | + \u{2189} | + [\u{2190}-\u{2199}] | + [\u{21B8}-\u{21B9}] | + \u{21D2} | + \u{21D4} | + \u{21E7} | + \u{2200} | + [\u{2202}-\u{2203}] | + [\u{2207}-\u{2208}] | + \u{220B} | + \u{220F} | + \u{2211} | + \u{2215} | + \u{221A} | + [\u{221D}-\u{2220}] | + \u{2223} | + \u{2225} | + [\u{2227}-\u{222C}] | + \u{222E} | + [\u{2234}-\u{2237}] | + [\u{223C}-\u{223D}] | + \u{2248} | + \u{224C} | + \u{2252} | + [\u{2260}-\u{2261}] | + [\u{2264}-\u{2267}] | + [\u{226A}-\u{226B}] | + [\u{226E}-\u{226F}] | + [\u{2282}-\u{2283}] | + [\u{2286}-\u{2287}] | + \u{2295} | + \u{2299} | + \u{22A5} | + \u{22BF} | + \u{2312} | + [\u{2460}-\u{24E9}] | + [\u{24EB}-\u{254B}] | + [\u{2550}-\u{2573}] | + [\u{2580}-\u{258F}] | + [\u{2592}-\u{2595}] | + [\u{25A0}-\u{25A1}] | + [\u{25A3}-\u{25A9}] | + [\u{25B2}-\u{25B3}] | + [\u{25B6}-\u{25B7}] | + [\u{25BC}-\u{25BD}] | + [\u{25C0}-\u{25C1}] | + [\u{25C6}-\u{25C8}] | + \u{25CB} | + [\u{25CE}-\u{25D1}] | + [\u{25E2}-\u{25E5}] | + \u{25EF} | + [\u{2605}-\u{2606}] | + \u{2609} | + [\u{260E}-\u{260F}] | + \u{261C} | + \u{261E} | + \u{2640} | + \u{2642} | + [\u{2660}-\u{2661}] | + [\u{2663}-\u{2665}] | + [\u{2667}-\u{266A}] | + [\u{266C}-\u{266D}] | + \u{266F} | + [\u{269E}-\u{269F}] | + \u{26BF} | + [\u{26C6}-\u{26CD}] | + [\u{26CF}-\u{26D3}] | + [\u{26D5}-\u{26E1}] | + \u{26E3} | + [\u{26E8}-\u{26E9}] | + [\u{26EB}-\u{26F1}] | + \u{26F4} | + [\u{26F6}-\u{26F9}] | + [\u{26FB}-\u{26FC}] | + [\u{26FE}-\u{26FF}] | + \u{273D} | + [\u{2776}-\u{277F}] | + [\u{2B56}-\u{2B59}] | + [\u{3248}-\u{324F}] | + [\u{E000}-\u{F8FF}] | + [\u{FE00}-\u{FE0F}] | + \u{FFFD} | + [\u{1F100}-\u{1F10A}] | + [\u{1F110}-\u{1F12D}] | + [\u{1F130}-\u{1F169}] | + [\u{1F170}-\u{1F18D}] | + [\u{1F18F}-\u{1F190}] | + [\u{1F19B}-\u{1F1AC}] | + [\u{E0100}-\u{E01EF}] | + [\u{F0000}-\u{FFFFD}] | + [\u{100000}-\u{10FFFD}] + )/x + + # Neutral + TYPE_N = /^( + [\u{0000}-\u{001F}] | + [\u{007F}-\u{00A0}] | + \u{00A9} | + \u{00AB} | + \u{00B5} | + \u{00BB} | + [\u{00C0}-\u{00C5}] | + [\u{00C7}-\u{00CF}] | + [\u{00D1}-\u{00D6}] | + [\u{00D9}-\u{00DD}] | + [\u{00E2}-\u{00E5}] | + \u{00E7} | + \u{00EB} | + [\u{00EE}-\u{00EF}] | + \u{00F1} | + [\u{00F4}-\u{00F6}] | + \u{00FB} | + \u{00FD} | + [\u{00FF}-\u{0100}] | + [\u{0102}-\u{0110}] | + \u{0112} | + [\u{0114}-\u{011A}] | + [\u{011C}-\u{0125}] | + [\u{0128}-\u{012A}] | + [\u{012C}-\u{0130}] | + [\u{0134}-\u{0137}] | + [\u{0139}-\u{013E}] | + \u{0143} | + [\u{0145}-\u{0147}] | + \u{014C} | + [\u{014E}-\u{0151}] | + [\u{0154}-\u{0165}] | + [\u{0168}-\u{016A}] | + [\u{016C}-\u{01CD}] | + \u{01CF} | + \u{01D1} | + \u{01D3} | + \u{01D5} | + \u{01D7} | + \u{01D9} | + \u{01DB} | + [\u{01DD}-\u{0250}] | + [\u{0252}-\u{0260}] | + [\u{0262}-\u{02C3}] | + [\u{02C5}-\u{02C6}] | + \u{02C8} | + \u{02CC} | + [\u{02CE}-\u{02CF}] | + [\u{02D1}-\u{02D7}] | + \u{02DC} | + \u{02DE} | + [\u{02E0}-\u{02FF}] | + [\u{0370}-\u{0377}] | + [\u{037A}-\u{037F}] | + [\u{0384}-\u{038A}] | + \u{038C} | + [\u{038E}-\u{0390}] | + [\u{03AA}-\u{03B0}] | + \u{03C2} | + [\u{03CA}-\u{0400}] | + [\u{0402}-\u{040F}] | + \u{0450} | + [\u{0452}-\u{052F}] | + [\u{0531}-\u{0556}] | + [\u{0559}-\u{058A}] | + [\u{058D}-\u{058F}] | + [\u{0591}-\u{05C7}] | + [\u{05D0}-\u{05EA}] | + [\u{05EF}-\u{05F4}] | + [\u{0600}-\u{061C}] | + [\u{061E}-\u{070D}] | + [\u{070F}-\u{074A}] | + [\u{074D}-\u{07B1}] | + [\u{07C0}-\u{07FA}] | + [\u{07FD}-\u{082D}] | + [\u{0830}-\u{083E}] | + [\u{0840}-\u{085B}] | + \u{085E} | + [\u{0860}-\u{086A}] | + [\u{08A0}-\u{08B4}] | + [\u{08B6}-\u{08BD}] | + [\u{08D3}-\u{0983}] | + [\u{0985}-\u{098C}] | + [\u{098F}-\u{0990}] | + [\u{0993}-\u{09A8}] | + [\u{09AA}-\u{09B0}] | + \u{09B2} | + [\u{09B6}-\u{09B9}] | + [\u{09BC}-\u{09C4}] | + [\u{09C7}-\u{09C8}] | + [\u{09CB}-\u{09CE}] | + \u{09D7} | + [\u{09DC}-\u{09DD}] | + [\u{09DF}-\u{09E3}] | + [\u{09E6}-\u{09FE}] | + [\u{0A01}-\u{0A03}] | + [\u{0A05}-\u{0A0A}] | + [\u{0A0F}-\u{0A10}] | + [\u{0A13}-\u{0A28}] | + [\u{0A2A}-\u{0A30}] | + [\u{0A32}-\u{0A33}] | + [\u{0A35}-\u{0A36}] | + [\u{0A38}-\u{0A39}] | + \u{0A3C} | + [\u{0A3E}-\u{0A42}] | + [\u{0A47}-\u{0A48}] | + [\u{0A4B}-\u{0A4D}] | + \u{0A51} | + [\u{0A59}-\u{0A5C}] | + \u{0A5E} | + [\u{0A66}-\u{0A76}] | + [\u{0A81}-\u{0A83}] | + [\u{0A85}-\u{0A8D}] | + [\u{0A8F}-\u{0A91}] | + [\u{0A93}-\u{0AA8}] | + [\u{0AAA}-\u{0AB0}] | + [\u{0AB2}-\u{0AB3}] | + [\u{0AB5}-\u{0AB9}] | + [\u{0ABC}-\u{0AC5}] | + [\u{0AC7}-\u{0AC9}] | + [\u{0ACB}-\u{0ACD}] | + \u{0AD0} | + [\u{0AE0}-\u{0AE3}] | + [\u{0AE6}-\u{0AF1}] | + [\u{0AF9}-\u{0AFF}] | + [\u{0B01}-\u{0B03}] | + [\u{0B05}-\u{0B0C}] | + [\u{0B0F}-\u{0B10}] | + [\u{0B13}-\u{0B28}] | + [\u{0B2A}-\u{0B30}] | + [\u{0B32}-\u{0B33}] | + [\u{0B35}-\u{0B39}] | + [\u{0B3C}-\u{0B44}] | + [\u{0B47}-\u{0B48}] | + [\u{0B4B}-\u{0B4D}] | + [\u{0B56}-\u{0B57}] | + [\u{0B5C}-\u{0B5D}] | + [\u{0B5F}-\u{0B63}] | + [\u{0B66}-\u{0B77}] | + [\u{0B82}-\u{0B83}] | + [\u{0B85}-\u{0B8A}] | + [\u{0B8E}-\u{0B90}] | + [\u{0B92}-\u{0B95}] | + [\u{0B99}-\u{0B9A}] | + \u{0B9C} | + [\u{0B9E}-\u{0B9F}] | + [\u{0BA3}-\u{0BA4}] | + [\u{0BA8}-\u{0BAA}] | + [\u{0BAE}-\u{0BB9}] | + [\u{0BBE}-\u{0BC2}] | + [\u{0BC6}-\u{0BC8}] | + [\u{0BCA}-\u{0BCD}] | + \u{0BD0} | + \u{0BD7} | + [\u{0BE6}-\u{0BFA}] | + [\u{0C00}-\u{0C0C}] | + [\u{0C0E}-\u{0C10}] | + [\u{0C12}-\u{0C28}] | + [\u{0C2A}-\u{0C39}] | + [\u{0C3D}-\u{0C44}] | + [\u{0C46}-\u{0C48}] | + [\u{0C4A}-\u{0C4D}] | + [\u{0C55}-\u{0C56}] | + [\u{0C58}-\u{0C5A}] | + [\u{0C60}-\u{0C63}] | + [\u{0C66}-\u{0C6F}] | + [\u{0C77}-\u{0C8C}] | + [\u{0C8E}-\u{0C90}] | + [\u{0C92}-\u{0CA8}] | + [\u{0CAA}-\u{0CB3}] | + [\u{0CB5}-\u{0CB9}] | + [\u{0CBC}-\u{0CC4}] | + [\u{0CC6}-\u{0CC8}] | + [\u{0CCA}-\u{0CCD}] | + [\u{0CD5}-\u{0CD6}] | + \u{0CDE} | + [\u{0CE0}-\u{0CE3}] | + [\u{0CE6}-\u{0CEF}] | + [\u{0CF1}-\u{0CF2}] | + [\u{0D00}-\u{0D03}] | + [\u{0D05}-\u{0D0C}] | + [\u{0D0E}-\u{0D10}] | + [\u{0D12}-\u{0D44}] | + [\u{0D46}-\u{0D48}] | + [\u{0D4A}-\u{0D4F}] | + [\u{0D54}-\u{0D63}] | + [\u{0D66}-\u{0D7F}] | + [\u{0D82}-\u{0D83}] | + [\u{0D85}-\u{0D96}] | + [\u{0D9A}-\u{0DB1}] | + [\u{0DB3}-\u{0DBB}] | + \u{0DBD} | + [\u{0DC0}-\u{0DC6}] | + \u{0DCA} | + [\u{0DCF}-\u{0DD4}] | + \u{0DD6} | + [\u{0DD8}-\u{0DDF}] | + [\u{0DE6}-\u{0DEF}] | + [\u{0DF2}-\u{0DF4}] | + [\u{0E01}-\u{0E3A}] | + [\u{0E3F}-\u{0E5B}] | + [\u{0E81}-\u{0E82}] | + \u{0E84} | + [\u{0E86}-\u{0E8A}] | + [\u{0E8C}-\u{0EA3}] | + \u{0EA5} | + [\u{0EA7}-\u{0EBD}] | + [\u{0EC0}-\u{0EC4}] | + \u{0EC6} | + [\u{0EC8}-\u{0ECD}] | + [\u{0ED0}-\u{0ED9}] | + [\u{0EDC}-\u{0EDF}] | + [\u{0F00}-\u{0F47}] | + [\u{0F49}-\u{0F6C}] | + [\u{0F71}-\u{0F97}] | + [\u{0F99}-\u{0FBC}] | + [\u{0FBE}-\u{0FCC}] | + [\u{0FCE}-\u{0FDA}] | + [\u{1000}-\u{10C5}] | + \u{10C7} | + \u{10CD} | + [\u{10D0}-\u{10FF}] | + [\u{1160}-\u{1248}] | + [\u{124A}-\u{124D}] | + [\u{1250}-\u{1256}] | + \u{1258} | + [\u{125A}-\u{125D}] | + [\u{1260}-\u{1288}] | + [\u{128A}-\u{128D}] | + [\u{1290}-\u{12B0}] | + [\u{12B2}-\u{12B5}] | + [\u{12B8}-\u{12BE}] | + \u{12C0} | + [\u{12C2}-\u{12C5}] | + [\u{12C8}-\u{12D6}] | + [\u{12D8}-\u{1310}] | + [\u{1312}-\u{1315}] | + [\u{1318}-\u{135A}] | + [\u{135D}-\u{137C}] | + [\u{1380}-\u{1399}] | + [\u{13A0}-\u{13F5}] | + [\u{13F8}-\u{13FD}] | + [\u{1400}-\u{169C}] | + [\u{16A0}-\u{16F8}] | + [\u{1700}-\u{170C}] | + [\u{170E}-\u{1714}] | + [\u{1720}-\u{1736}] | + [\u{1740}-\u{1753}] | + [\u{1760}-\u{176C}] | + [\u{176E}-\u{1770}] | + [\u{1772}-\u{1773}] | + [\u{1780}-\u{17DD}] | + [\u{17E0}-\u{17E9}] | + [\u{17F0}-\u{17F9}] | + [\u{1800}-\u{180E}] | + [\u{1810}-\u{1819}] | + [\u{1820}-\u{1878}] | + [\u{1880}-\u{18AA}] | + [\u{18B0}-\u{18F5}] | + [\u{1900}-\u{191E}] | + [\u{1920}-\u{192B}] | + [\u{1930}-\u{193B}] | + \u{1940} | + [\u{1944}-\u{196D}] | + [\u{1970}-\u{1974}] | + [\u{1980}-\u{19AB}] | + [\u{19B0}-\u{19C9}] | + [\u{19D0}-\u{19DA}] | + [\u{19DE}-\u{1A1B}] | + [\u{1A1E}-\u{1A5E}] | + [\u{1A60}-\u{1A7C}] | + [\u{1A7F}-\u{1A89}] | + [\u{1A90}-\u{1A99}] | + [\u{1AA0}-\u{1AAD}] | + [\u{1AB0}-\u{1ABE}] | + [\u{1B00}-\u{1B4B}] | + [\u{1B50}-\u{1B7C}] | + [\u{1B80}-\u{1BF3}] | + [\u{1BFC}-\u{1C37}] | + [\u{1C3B}-\u{1C49}] | + [\u{1C4D}-\u{1C88}] | + [\u{1C90}-\u{1CBA}] | + [\u{1CBD}-\u{1CC7}] | + [\u{1CD0}-\u{1CFA}] | + [\u{1D00}-\u{1DF9}] | + [\u{1DFB}-\u{1F15}] | + [\u{1F18}-\u{1F1D}] | + [\u{1F20}-\u{1F45}] | + [\u{1F48}-\u{1F4D}] | + [\u{1F50}-\u{1F57}] | + \u{1F59} | + \u{1F5B} | + \u{1F5D} | + [\u{1F5F}-\u{1F7D}] | + [\u{1F80}-\u{1FB4}] | + [\u{1FB6}-\u{1FC4}] | + [\u{1FC6}-\u{1FD3}] | + [\u{1FD6}-\u{1FDB}] | + [\u{1FDD}-\u{1FEF}] | + [\u{1FF2}-\u{1FF4}] | + [\u{1FF6}-\u{1FFE}] | + [\u{2000}-\u{200F}] | + [\u{2011}-\u{2012}] | + \u{2017} | + [\u{201A}-\u{201B}] | + [\u{201E}-\u{201F}] | + \u{2023} | + [\u{2028}-\u{202F}] | + \u{2031} | + \u{2034} | + [\u{2036}-\u{203A}] | + [\u{203C}-\u{203D}] | + [\u{203F}-\u{2064}] | + [\u{2066}-\u{2071}] | + [\u{2075}-\u{207E}] | + \u{2080} | + [\u{2085}-\u{208E}] | + [\u{2090}-\u{209C}] | + [\u{20A0}-\u{20A8}] | + [\u{20AA}-\u{20AB}] | + [\u{20AD}-\u{20BF}] | + [\u{20D0}-\u{20F0}] | + [\u{2100}-\u{2102}] | + \u{2104} | + [\u{2106}-\u{2108}] | + [\u{210A}-\u{2112}] | + [\u{2114}-\u{2115}] | + [\u{2117}-\u{2120}] | + [\u{2123}-\u{2125}] | + [\u{2127}-\u{212A}] | + [\u{212C}-\u{2152}] | + [\u{2155}-\u{215A}] | + \u{215F} | + [\u{216C}-\u{216F}] | + [\u{217A}-\u{2188}] | + [\u{218A}-\u{218B}] | + [\u{219A}-\u{21B7}] | + [\u{21BA}-\u{21D1}] | + \u{21D3} | + [\u{21D5}-\u{21E6}] | + [\u{21E8}-\u{21FF}] | + \u{2201} | + [\u{2204}-\u{2206}] | + [\u{2209}-\u{220A}] | + [\u{220C}-\u{220E}] | + \u{2210} | + [\u{2212}-\u{2214}] | + [\u{2216}-\u{2219}] | + [\u{221B}-\u{221C}] | + [\u{2221}-\u{2222}] | + \u{2224} | + \u{2226} | + \u{222D} | + [\u{222F}-\u{2233}] | + [\u{2238}-\u{223B}] | + [\u{223E}-\u{2247}] | + [\u{2249}-\u{224B}] | + [\u{224D}-\u{2251}] | + [\u{2253}-\u{225F}] | + [\u{2262}-\u{2263}] | + [\u{2268}-\u{2269}] | + [\u{226C}-\u{226D}] | + [\u{2270}-\u{2281}] | + [\u{2284}-\u{2285}] | + [\u{2288}-\u{2294}] | + [\u{2296}-\u{2298}] | + [\u{229A}-\u{22A4}] | + [\u{22A6}-\u{22BE}] | + [\u{22C0}-\u{2311}] | + [\u{2313}-\u{2319}] | + [\u{231C}-\u{2328}] | + [\u{232B}-\u{23E8}] | + [\u{23ED}-\u{23EF}] | + [\u{23F1}-\u{23F2}] | + [\u{23F4}-\u{2426}] | + [\u{2440}-\u{244A}] | + \u{24EA} | + [\u{254C}-\u{254F}] | + [\u{2574}-\u{257F}] | + [\u{2590}-\u{2591}] | + [\u{2596}-\u{259F}] | + \u{25A2} | + [\u{25AA}-\u{25B1}] | + [\u{25B4}-\u{25B5}] | + [\u{25B8}-\u{25BB}] | + [\u{25BE}-\u{25BF}] | + [\u{25C2}-\u{25C5}] | + [\u{25C9}-\u{25CA}] | + [\u{25CC}-\u{25CD}] | + [\u{25D2}-\u{25E1}] | + [\u{25E6}-\u{25EE}] | + [\u{25F0}-\u{25FC}] | + [\u{25FF}-\u{2604}] | + [\u{2607}-\u{2608}] | + [\u{260A}-\u{260D}] | + [\u{2610}-\u{2613}] | + [\u{2616}-\u{261B}] | + \u{261D} | + [\u{261F}-\u{263F}] | + \u{2641} | + [\u{2643}-\u{2647}] | + [\u{2654}-\u{265F}] | + \u{2662} | + \u{2666} | + \u{266B} | + \u{266E} | + [\u{2670}-\u{267E}] | + [\u{2680}-\u{2692}] | + [\u{2694}-\u{269D}] | + \u{26A0} | + [\u{26A2}-\u{26A9}] | + [\u{26AC}-\u{26BC}] | + [\u{26C0}-\u{26C3}] | + \u{26E2} | + [\u{26E4}-\u{26E7}] | + [\u{2700}-\u{2704}] | + [\u{2706}-\u{2709}] | + [\u{270C}-\u{2727}] | + [\u{2729}-\u{273C}] | + [\u{273E}-\u{274B}] | + \u{274D} | + [\u{274F}-\u{2752}] | + \u{2756} | + [\u{2758}-\u{2775}] | + [\u{2780}-\u{2794}] | + [\u{2798}-\u{27AF}] | + [\u{27B1}-\u{27BE}] | + [\u{27C0}-\u{27E5}] | + [\u{27EE}-\u{2984}] | + [\u{2987}-\u{2B1A}] | + [\u{2B1D}-\u{2B4F}] | + [\u{2B51}-\u{2B54}] | + [\u{2B5A}-\u{2B73}] | + [\u{2B76}-\u{2B95}] | + [\u{2B98}-\u{2C2E}] | + [\u{2C30}-\u{2C5E}] | + [\u{2C60}-\u{2CF3}] | + [\u{2CF9}-\u{2D25}] | + \u{2D27} | + \u{2D2D} | + [\u{2D30}-\u{2D67}] | + [\u{2D6F}-\u{2D70}] | + [\u{2D7F}-\u{2D96}] | + [\u{2DA0}-\u{2DA6}] | + [\u{2DA8}-\u{2DAE}] | + [\u{2DB0}-\u{2DB6}] | + [\u{2DB8}-\u{2DBE}] | + [\u{2DC0}-\u{2DC6}] | + [\u{2DC8}-\u{2DCE}] | + [\u{2DD0}-\u{2DD6}] | + [\u{2DD8}-\u{2DDE}] | + [\u{2DE0}-\u{2E4F}] | + \u{303F} | + [\u{4DC0}-\u{4DFF}] | + [\u{A4D0}-\u{A62B}] | + [\u{A640}-\u{A6F7}] | + [\u{A700}-\u{A7BF}] | + [\u{A7C2}-\u{A7C6}] | + [\u{A7F7}-\u{A82B}] | + [\u{A830}-\u{A839}] | + [\u{A840}-\u{A877}] | + [\u{A880}-\u{A8C5}] | + [\u{A8CE}-\u{A8D9}] | + [\u{A8E0}-\u{A953}] | + \u{A95F} | + [\u{A980}-\u{A9CD}] | + [\u{A9CF}-\u{A9D9}] | + [\u{A9DE}-\u{A9FE}] | + [\u{AA00}-\u{AA36}] | + [\u{AA40}-\u{AA4D}] | + [\u{AA50}-\u{AA59}] | + [\u{AA5C}-\u{AAC2}] | + [\u{AADB}-\u{AAF6}] | + [\u{AB01}-\u{AB06}] | + [\u{AB09}-\u{AB0E}] | + [\u{AB11}-\u{AB16}] | + [\u{AB20}-\u{AB26}] | + [\u{AB28}-\u{AB2E}] | + [\u{AB30}-\u{AB67}] | + [\u{AB70}-\u{ABED}] | + [\u{ABF0}-\u{ABF9}] | + [\u{D7B0}-\u{D7C6}] | + [\u{D7CB}-\u{D7FB}] | + [\u{FB00}-\u{FB06}] | + [\u{FB13}-\u{FB17}] | + [\u{FB1D}-\u{FB36}] | + [\u{FB38}-\u{FB3C}] | + \u{FB3E} | + [\u{FB40}-\u{FB41}] | + [\u{FB43}-\u{FB44}] | + [\u{FB46}-\u{FBC1}] | + [\u{FBD3}-\u{FD3F}] | + [\u{FD50}-\u{FD8F}] | + [\u{FD92}-\u{FDC7}] | + [\u{FDF0}-\u{FDFD}] | + [\u{FE20}-\u{FE2F}] | + [\u{FE70}-\u{FE74}] | + [\u{FE76}-\u{FEFC}] | + \u{FEFF} | + [\u{FFF9}-\u{FFFC}] | + [\u{10000}-\u{1000B}] | + [\u{1000D}-\u{10026}] | + [\u{10028}-\u{1003A}] | + [\u{1003C}-\u{1003D}] | + [\u{1003F}-\u{1004D}] | + [\u{10050}-\u{1005D}] | + [\u{10080}-\u{100FA}] | + [\u{10100}-\u{10102}] | + [\u{10107}-\u{10133}] | + [\u{10137}-\u{1018E}] | + [\u{10190}-\u{1019B}] | + \u{101A0} | + [\u{101D0}-\u{101FD}] | + [\u{10280}-\u{1029C}] | + [\u{102A0}-\u{102D0}] | + [\u{102E0}-\u{102FB}] | + [\u{10300}-\u{10323}] | + [\u{1032D}-\u{1034A}] | + [\u{10350}-\u{1037A}] | + [\u{10380}-\u{1039D}] | + [\u{1039F}-\u{103C3}] | + [\u{103C8}-\u{103D5}] | + [\u{10400}-\u{1049D}] | + [\u{104A0}-\u{104A9}] | + [\u{104B0}-\u{104D3}] | + [\u{104D8}-\u{104FB}] | + [\u{10500}-\u{10527}] | + [\u{10530}-\u{10563}] | + \u{1056F} | + [\u{10600}-\u{10736}] | + [\u{10740}-\u{10755}] | + [\u{10760}-\u{10767}] | + [\u{10800}-\u{10805}] | + \u{10808} | + [\u{1080A}-\u{10835}] | + [\u{10837}-\u{10838}] | + \u{1083C} | + [\u{1083F}-\u{10855}] | + [\u{10857}-\u{1089E}] | + [\u{108A7}-\u{108AF}] | + [\u{108E0}-\u{108F2}] | + [\u{108F4}-\u{108F5}] | + [\u{108FB}-\u{1091B}] | + [\u{1091F}-\u{10939}] | + \u{1093F} | + [\u{10980}-\u{109B7}] | + [\u{109BC}-\u{109CF}] | + [\u{109D2}-\u{10A03}] | + [\u{10A05}-\u{10A06}] | + [\u{10A0C}-\u{10A13}] | + [\u{10A15}-\u{10A17}] | + [\u{10A19}-\u{10A35}] | + [\u{10A38}-\u{10A3A}] | + [\u{10A3F}-\u{10A48}] | + [\u{10A50}-\u{10A58}] | + [\u{10A60}-\u{10A9F}] | + [\u{10AC0}-\u{10AE6}] | + [\u{10AEB}-\u{10AF6}] | + [\u{10B00}-\u{10B35}] | + [\u{10B39}-\u{10B55}] | + [\u{10B58}-\u{10B72}] | + [\u{10B78}-\u{10B91}] | + [\u{10B99}-\u{10B9C}] | + [\u{10BA9}-\u{10BAF}] | + [\u{10C00}-\u{10C48}] | + [\u{10C80}-\u{10CB2}] | + [\u{10CC0}-\u{10CF2}] | + [\u{10CFA}-\u{10D27}] | + [\u{10D30}-\u{10D39}] | + [\u{10E60}-\u{10E7E}] | + [\u{10F00}-\u{10F27}] | + [\u{10F30}-\u{10F59}] | + [\u{10FE0}-\u{10FF6}] | + [\u{11000}-\u{1104D}] | + [\u{11052}-\u{1106F}] | + [\u{1107F}-\u{110C1}] | + \u{110CD} | + [\u{110D0}-\u{110E8}] | + [\u{110F0}-\u{110F9}] | + [\u{11100}-\u{11134}] | + [\u{11136}-\u{11146}] | + [\u{11150}-\u{11176}] | + [\u{11180}-\u{111CD}] | + [\u{111D0}-\u{111DF}] | + [\u{111E1}-\u{111F4}] | + [\u{11200}-\u{11211}] | + [\u{11213}-\u{1123E}] | + [\u{11280}-\u{11286}] | + \u{11288} | + [\u{1128A}-\u{1128D}] | + [\u{1128F}-\u{1129D}] | + [\u{1129F}-\u{112A9}] | + [\u{112B0}-\u{112EA}] | + [\u{112F0}-\u{112F9}] | + [\u{11300}-\u{11303}] | + [\u{11305}-\u{1130C}] | + [\u{1130F}-\u{11310}] | + [\u{11313}-\u{11328}] | + [\u{1132A}-\u{11330}] | + [\u{11332}-\u{11333}] | + [\u{11335}-\u{11339}] | + [\u{1133B}-\u{11344}] | + [\u{11347}-\u{11348}] | + [\u{1134B}-\u{1134D}] | + \u{11350} | + \u{11357} | + [\u{1135D}-\u{11363}] | + [\u{11366}-\u{1136C}] | + [\u{11370}-\u{11374}] | + [\u{11400}-\u{11459}] | + \u{1145B} | + [\u{1145D}-\u{1145F}] | + [\u{11480}-\u{114C7}] | + [\u{114D0}-\u{114D9}] | + [\u{11580}-\u{115B5}] | + [\u{115B8}-\u{115DD}] | + [\u{11600}-\u{11644}] | + [\u{11650}-\u{11659}] | + [\u{11660}-\u{1166C}] | + [\u{11680}-\u{116B8}] | + [\u{116C0}-\u{116C9}] | + [\u{11700}-\u{1171A}] | + [\u{1171D}-\u{1172B}] | + [\u{11730}-\u{1173F}] | + [\u{11800}-\u{1183B}] | + [\u{118A0}-\u{118F2}] | + \u{118FF} | + [\u{119A0}-\u{119A7}] | + [\u{119AA}-\u{119D7}] | + [\u{119DA}-\u{119E4}] | + [\u{11A00}-\u{11A47}] | + [\u{11A50}-\u{11AA2}] | + [\u{11AC0}-\u{11AF8}] | + [\u{11C00}-\u{11C08}] | + [\u{11C0A}-\u{11C36}] | + [\u{11C38}-\u{11C45}] | + [\u{11C50}-\u{11C6C}] | + [\u{11C70}-\u{11C8F}] | + [\u{11C92}-\u{11CA7}] | + [\u{11CA9}-\u{11CB6}] | + [\u{11D00}-\u{11D06}] | + [\u{11D08}-\u{11D09}] | + [\u{11D0B}-\u{11D36}] | + \u{11D3A} | + [\u{11D3C}-\u{11D3D}] | + [\u{11D3F}-\u{11D47}] | + [\u{11D50}-\u{11D59}] | + [\u{11D60}-\u{11D65}] | + [\u{11D67}-\u{11D68}] | + [\u{11D6A}-\u{11D8E}] | + [\u{11D90}-\u{11D91}] | + [\u{11D93}-\u{11D98}] | + [\u{11DA0}-\u{11DA9}] | + [\u{11EE0}-\u{11EF8}] | + [\u{11FC0}-\u{11FF1}] | + [\u{11FFF}-\u{12399}] | + [\u{12400}-\u{1246E}] | + [\u{12470}-\u{12474}] | + [\u{12480}-\u{12543}] | + [\u{13000}-\u{1342E}] | + [\u{13430}-\u{13438}] | + [\u{14400}-\u{14646}] | + [\u{16800}-\u{16A38}] | + [\u{16A40}-\u{16A5E}] | + [\u{16A60}-\u{16A69}] | + [\u{16A6E}-\u{16A6F}] | + [\u{16AD0}-\u{16AED}] | + [\u{16AF0}-\u{16AF5}] | + [\u{16B00}-\u{16B45}] | + [\u{16B50}-\u{16B59}] | + [\u{16B5B}-\u{16B61}] | + [\u{16B63}-\u{16B77}] | + [\u{16B7D}-\u{16B8F}] | + [\u{16E40}-\u{16E9A}] | + [\u{16F00}-\u{16F4A}] | + [\u{16F4F}-\u{16F87}] | + [\u{16F8F}-\u{16F9F}] | + [\u{1BC00}-\u{1BC6A}] | + [\u{1BC70}-\u{1BC7C}] | + [\u{1BC80}-\u{1BC88}] | + [\u{1BC90}-\u{1BC99}] | + [\u{1BC9C}-\u{1BCA3}] | + [\u{1D000}-\u{1D0F5}] | + [\u{1D100}-\u{1D126}] | + [\u{1D129}-\u{1D1E8}] | + [\u{1D200}-\u{1D245}] | + [\u{1D2E0}-\u{1D2F3}] | + [\u{1D300}-\u{1D356}] | + [\u{1D360}-\u{1D378}] | + [\u{1D400}-\u{1D454}] | + [\u{1D456}-\u{1D49C}] | + [\u{1D49E}-\u{1D49F}] | + \u{1D4A2} | + [\u{1D4A5}-\u{1D4A6}] | + [\u{1D4A9}-\u{1D4AC}] | + [\u{1D4AE}-\u{1D4B9}] | + \u{1D4BB} | + [\u{1D4BD}-\u{1D4C3}] | + [\u{1D4C5}-\u{1D505}] | + [\u{1D507}-\u{1D50A}] | + [\u{1D50D}-\u{1D514}] | + [\u{1D516}-\u{1D51C}] | + [\u{1D51E}-\u{1D539}] | + [\u{1D53B}-\u{1D53E}] | + [\u{1D540}-\u{1D544}] | + \u{1D546} | + [\u{1D54A}-\u{1D550}] | + [\u{1D552}-\u{1D6A5}] | + [\u{1D6A8}-\u{1D7CB}] | + [\u{1D7CE}-\u{1DA8B}] | + [\u{1DA9B}-\u{1DA9F}] | + [\u{1DAA1}-\u{1DAAF}] | + [\u{1E000}-\u{1E006}] | + [\u{1E008}-\u{1E018}] | + [\u{1E01B}-\u{1E021}] | + [\u{1E023}-\u{1E024}] | + [\u{1E026}-\u{1E02A}] | + [\u{1E100}-\u{1E12C}] | + [\u{1E130}-\u{1E13D}] | + [\u{1E140}-\u{1E149}] | + [\u{1E14E}-\u{1E14F}] | + [\u{1E2C0}-\u{1E2F9}] | + \u{1E2FF} | + [\u{1E800}-\u{1E8C4}] | + [\u{1E8C7}-\u{1E8D6}] | + [\u{1E900}-\u{1E94B}] | + [\u{1E950}-\u{1E959}] | + [\u{1E95E}-\u{1E95F}] | + [\u{1EC71}-\u{1ECB4}] | + [\u{1ED01}-\u{1ED3D}] | + [\u{1EE00}-\u{1EE03}] | + [\u{1EE05}-\u{1EE1F}] | + [\u{1EE21}-\u{1EE22}] | + \u{1EE24} | + \u{1EE27} | + [\u{1EE29}-\u{1EE32}] | + [\u{1EE34}-\u{1EE37}] | + \u{1EE39} | + \u{1EE3B} | + \u{1EE42} | + \u{1EE47} | + \u{1EE49} | + \u{1EE4B} | + [\u{1EE4D}-\u{1EE4F}] | + [\u{1EE51}-\u{1EE52}] | + \u{1EE54} | + \u{1EE57} | + \u{1EE59} | + \u{1EE5B} | + \u{1EE5D} | + \u{1EE5F} | + [\u{1EE61}-\u{1EE62}] | + \u{1EE64} | + [\u{1EE67}-\u{1EE6A}] | + [\u{1EE6C}-\u{1EE72}] | + [\u{1EE74}-\u{1EE77}] | + [\u{1EE79}-\u{1EE7C}] | + \u{1EE7E} | + [\u{1EE80}-\u{1EE89}] | + [\u{1EE8B}-\u{1EE9B}] | + [\u{1EEA1}-\u{1EEA3}] | + [\u{1EEA5}-\u{1EEA9}] | + [\u{1EEAB}-\u{1EEBB}] | + [\u{1EEF0}-\u{1EEF1}] | + [\u{1F000}-\u{1F003}] | + [\u{1F005}-\u{1F02B}] | + [\u{1F030}-\u{1F093}] | + [\u{1F0A0}-\u{1F0AE}] | + [\u{1F0B1}-\u{1F0BF}] | + [\u{1F0C1}-\u{1F0CE}] | + [\u{1F0D1}-\u{1F0F5}] | + [\u{1F10B}-\u{1F10C}] | + [\u{1F12E}-\u{1F12F}] | + [\u{1F16A}-\u{1F16C}] | + [\u{1F1E6}-\u{1F1FF}] | + [\u{1F321}-\u{1F32C}] | + \u{1F336} | + \u{1F37D} | + [\u{1F394}-\u{1F39F}] | + [\u{1F3CB}-\u{1F3CE}] | + [\u{1F3D4}-\u{1F3DF}] | + [\u{1F3F1}-\u{1F3F3}] | + [\u{1F3F5}-\u{1F3F7}] | + \u{1F43F} | + \u{1F441} | + [\u{1F4FD}-\u{1F4FE}] | + [\u{1F53E}-\u{1F54A}] | + \u{1F54F} | + [\u{1F568}-\u{1F579}] | + [\u{1F57B}-\u{1F594}] | + [\u{1F597}-\u{1F5A3}] | + [\u{1F5A5}-\u{1F5FA}] | + [\u{1F650}-\u{1F67F}] | + [\u{1F6C6}-\u{1F6CB}] | + [\u{1F6CD}-\u{1F6CF}] | + [\u{1F6D3}-\u{1F6D4}] | + [\u{1F6E0}-\u{1F6EA}] | + [\u{1F6F0}-\u{1F6F3}] | + [\u{1F700}-\u{1F773}] | + [\u{1F780}-\u{1F7D8}] | + [\u{1F800}-\u{1F80B}] | + [\u{1F810}-\u{1F847}] | + [\u{1F850}-\u{1F859}] | + [\u{1F860}-\u{1F887}] | + [\u{1F890}-\u{1F8AD}] | + [\u{1F900}-\u{1F90B}] | + [\u{1FA00}-\u{1FA53}] | + [\u{1FA60}-\u{1FA6D}] | + \u{E0001} | + [\u{E0020}-\u{E007F}] + )/x +end diff --git a/lib/reline/version.rb b/lib/reline/version.rb new file mode 100644 index 00000000000000..58a69a09a0b892 --- /dev/null +++ b/lib/reline/version.rb @@ -0,0 +1,3 @@ +module Reline + VERSION = '0.0.0' +end diff --git a/lib/reline/windows.rb b/lib/reline/windows.rb new file mode 100644 index 00000000000000..868679ef9b5878 --- /dev/null +++ b/lib/reline/windows.rb @@ -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 diff --git a/spec/ruby/library/readline/spec_helper.rb b/spec/ruby/library/readline/spec_helper.rb index 56077c53c4a1cb..32d820f7ac7ba8 100644 --- a/spec/ruby/library/readline/spec_helper.rb +++ b/spec/ruby/library/readline/spec_helper.rb @@ -4,8 +4,8 @@ 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 diff --git a/test/reline/helper.rb b/test/reline/helper.rb new file mode 100644 index 00000000000000..2dced82a125ea3 --- /dev/null +++ b/test/reline/helper.rb @@ -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 diff --git a/test/reline/test_config.rb b/test/reline/test_config.rb new file mode 100644 index 00000000000000..bb06757901c7f9 --- /dev/null +++ b/test/reline/test_config.rb @@ -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 diff --git a/test/reline/test_key_actor_emacs.rb b/test/reline/test_key_actor_emacs.rb new file mode 100644 index 00000000000000..f4dfb952f54c98 --- /dev/null +++ b/test/reline/test_key_actor_emacs.rb @@ -0,0 +1,1166 @@ +require_relative 'helper' + +class Reline::KeyActor::Emacs::Test < Reline::TestCase + def setup + @prompt = '> ' + @config = Reline::Config.new # Emacs mode is default + @line_editor = Reline::LineEditor.new( + @config, @prompt, + (RELINE_TEST_ENCODING rescue Encoding.default_external)) + @line_editor.retrieve_completion_block = Reline.method(:retrieve_completion_block) + end + + def test_ed_insert_one + input_keys('a') + assert_line('a') + assert_byte_pointer_size('a') + assert_cursor(1) + assert_cursor_max(1) + end + + def test_ed_insert_two + input_keys('ab') + assert_line('ab') + assert_byte_pointer_size('ab') + assert_cursor(2) + assert_cursor_max(2) + end + + def test_ed_insert_mbchar_one + input_keys('か') + assert_line('か') + assert_byte_pointer_size('か') + assert_cursor(2) + assert_cursor_max(2) + end + + def test_ed_insert_mbchar_two + input_keys('かき') + assert_line('かき') + assert_byte_pointer_size('かき') + assert_cursor(4) + assert_cursor_max(4) + end + + def test_ed_insert_for_mbchar_by_plural_code_points + input_keys("か\u3099") + assert_line("か\u3099") + assert_byte_pointer_size("か\u3099") + assert_cursor(2) + assert_cursor_max(2) + end + + def test_ed_insert_for_plural_mbchar_by_plural_code_points + input_keys("か\u3099き\u3099") + assert_line("か\u3099き\u3099") + assert_byte_pointer_size("か\u3099き\u3099") + assert_cursor(4) + assert_cursor_max(4) + end + + def test_move_next_and_prev + input_keys('abd') + assert_byte_pointer_size('abd') + assert_cursor(3) + assert_cursor_max(3) + input_keys("\C-b", false) + assert_byte_pointer_size('ab') + assert_cursor(2) + assert_cursor_max(3) + input_keys("\C-b", false) + assert_byte_pointer_size('a') + assert_cursor(1) + assert_cursor_max(3) + input_keys("\C-f", false) + assert_byte_pointer_size('ab') + assert_cursor(2) + assert_cursor_max(3) + input_keys('c') + assert_byte_pointer_size('abc') + assert_cursor(3) + assert_cursor_max(4) + assert_line('abcd') + end + + def test_move_next_and_prev_for_mbchar + input_keys('かきけ') + assert_byte_pointer_size('かきけ') + assert_cursor(6) + assert_cursor_max(6) + input_keys("\C-b", false) + assert_byte_pointer_size('かき') + assert_cursor(4) + assert_cursor_max(6) + input_keys("\C-b", false) + assert_byte_pointer_size('か') + assert_cursor(2) + assert_cursor_max(6) + input_keys("\C-f", false) + assert_byte_pointer_size('かき') + assert_cursor(4) + assert_cursor_max(6) + input_keys('く') + assert_byte_pointer_size('かきく') + assert_cursor(6) + assert_cursor_max(8) + assert_line('かきくけ') + end + + def test_move_next_and_prev_for_mbchar_by_plural_code_points + input_keys("か\u3099き\u3099け\u3099") + assert_byte_pointer_size("か\u3099き\u3099け\u3099") + assert_cursor(6) + assert_cursor_max(6) + input_keys("\C-b", false) + assert_byte_pointer_size("か\u3099き\u3099") + assert_cursor(4) + assert_cursor_max(6) + input_keys("\C-b", false) + assert_byte_pointer_size("か\u3099") + assert_cursor(2) + assert_cursor_max(6) + input_keys("\C-f", false) + assert_byte_pointer_size("か\u3099き\u3099") + assert_cursor(4) + assert_cursor_max(6) + input_keys("く\u3099") + assert_byte_pointer_size("か\u3099き\u3099く\u3099") + assert_cursor(6) + assert_cursor_max(8) + assert_line("か\u3099き\u3099く\u3099け\u3099") + end + + def test_move_to_beg_end + input_keys('bcd') + assert_byte_pointer_size('bcd') + assert_cursor(3) + assert_cursor_max(3) + input_keys("\C-a", false) + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(3) + input_keys('a') + assert_byte_pointer_size('a') + assert_cursor(1) + assert_cursor_max(4) + input_keys("\C-e", false) + assert_byte_pointer_size('abcd') + assert_cursor(4) + assert_cursor_max(4) + input_keys('e') + assert_byte_pointer_size('abcde') + assert_cursor(5) + assert_cursor_max(5) + assert_line('abcde') + end + + def test_ed_newline_with_cr + input_keys('ab') + assert_byte_pointer_size('ab') + assert_cursor(2) + assert_cursor_max(2) + refute(@line_editor.finished?) + input_keys("\C-m", false) + assert_line('ab') + assert(@line_editor.finished?) + end + + def test_ed_newline_with_lf + input_keys('ab') + assert_byte_pointer_size('ab') + assert_cursor(2) + assert_cursor_max(2) + refute(@line_editor.finished?) + input_keys("\C-j", false) + assert_line('ab') + assert(@line_editor.finished?) + end + + def test_em_delete_prev_char + input_keys('ab') + assert_byte_pointer_size('ab') + assert_cursor(2) + assert_cursor_max(2) + input_keys("\C-h", false) + assert_byte_pointer_size('a') + assert_cursor(1) + assert_cursor_max(1) + assert_line('a') + end + + def test_em_delete_prev_char_for_mbchar + input_keys('かき') + assert_byte_pointer_size('かき') + assert_cursor(4) + assert_cursor_max(4) + input_keys("\C-h", false) + assert_byte_pointer_size('か') + assert_cursor(2) + assert_cursor_max(2) + assert_line('か') + end + + def test_em_delete_prev_char_for_mbchar_by_plural_code_points + input_keys("か\u3099き\u3099") + assert_byte_pointer_size("か\u3099き\u3099") + assert_cursor(4) + assert_cursor_max(4) + input_keys("\C-h", false) + assert_byte_pointer_size("か\u3099") + assert_cursor(2) + assert_cursor_max(2) + assert_line("か\u3099") + end + + def test_ed_kill_line + input_keys("\C-k", false) + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(0) + assert_line('') + input_keys('abc') + assert_byte_pointer_size('abc') + assert_cursor(3) + assert_cursor_max(3) + input_keys("\C-k", false) + assert_byte_pointer_size('abc') + assert_cursor(3) + assert_cursor_max(3) + assert_line('abc') + input_keys("\C-b\C-k", false) + assert_byte_pointer_size('ab') + assert_cursor(2) + assert_cursor_max(2) + assert_line('ab') + end + + def test_em_kill_line + input_keys("\C-u", false) + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(0) + assert_line('') + input_keys('abc') + assert_byte_pointer_size('abc') + assert_cursor(3) + assert_cursor_max(3) + input_keys("\C-u", false) + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(0) + assert_line('') + input_keys('abc') + input_keys("\C-b\C-u", false) + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(1) + assert_line('c') + input_keys("\C-u", false) + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(1) + assert_line('c') + end + + def test_ed_move_to_beg + input_keys('abd') + assert_byte_pointer_size('abd') + assert_cursor(3) + assert_cursor_max(3) + input_keys("\C-b", false) + assert_byte_pointer_size('ab') + assert_cursor(2) + assert_cursor_max(3) + input_keys('c') + assert_byte_pointer_size('abc') + assert_cursor(3) + assert_cursor_max(4) + input_keys("\C-a", false) + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(4) + input_keys('012') + assert_byte_pointer_size('012') + assert_cursor(3) + assert_cursor_max(7) + assert_line('012abcd') + input_keys("\C-a", false) + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(7) + input_keys('ABC') + assert_byte_pointer_size('ABC') + assert_cursor(3) + assert_cursor_max(10) + assert_line('ABC012abcd') + input_keys("\C-f" * 10 + "\C-a", false) + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(10) + input_keys('a') + assert_byte_pointer_size('a') + assert_cursor(1) + assert_cursor_max(11) + assert_line('aABC012abcd') + end + + def test_ed_move_to_end + input_keys('abd') + assert_byte_pointer_size('abd') + assert_cursor(3) + assert_cursor_max(3) + input_keys("\C-b", false) + assert_byte_pointer_size('ab') + assert_cursor(2) + assert_cursor_max(3) + input_keys('c') + assert_byte_pointer_size('abc') + assert_cursor(3) + assert_cursor_max(4) + input_keys("\C-e", false) + assert_byte_pointer_size('abcd') + assert_cursor(4) + assert_cursor_max(4) + input_keys('012') + assert_byte_pointer_size('abcd012') + assert_cursor(7) + assert_cursor_max(7) + assert_line('abcd012') + input_keys("\C-e", false) + assert_byte_pointer_size('abcd012') + assert_cursor(7) + assert_cursor_max(7) + input_keys('ABC') + assert_byte_pointer_size('abcd012ABC') + assert_cursor(10) + assert_cursor_max(10) + assert_line('abcd012ABC') + input_keys("\C-b" * 10 + "\C-e", false) + assert_byte_pointer_size('abcd012ABC') + assert_cursor(10) + assert_cursor_max(10) + input_keys('a') + assert_byte_pointer_size('abcd012ABCa') + assert_cursor(11) + assert_cursor_max(11) + assert_line('abcd012ABCa') + end + + def test_em_delete_or_list + input_keys('ab') + assert_byte_pointer_size('ab') + assert_cursor(2) + assert_cursor_max(2) + input_keys("\C-a", false) + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(2) + input_keys("\C-d", false) + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(1) + assert_line('b') + end + + def test_em_delete_or_list_for_mbchar + input_keys('かき') + assert_byte_pointer_size('かき') + assert_cursor(4) + assert_cursor_max(4) + input_keys("\C-a", false) + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(4) + input_keys("\C-d", false) + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(2) + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(2) + assert_line('き') + end + + def test_em_delete_or_list_for_mbchar_by_plural_code_points + input_keys("か\u3099き\u3099") + assert_byte_pointer_size("か\u3099き\u3099") + assert_cursor(4) + assert_cursor_max(4) + input_keys("\C-a", false) + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(4) + input_keys("\C-d", false) + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(2) + assert_line("き\u3099") + end + + def test_ed_clear_screen + refute(@line_editor.instance_variable_get(:@cleared)) + input_keys("\C-l", false) + assert(@line_editor.instance_variable_get(:@cleared)) + end + + def test_ed_clear_screen_with_inputed + input_keys('abc') + input_keys("\C-b", false) + refute(@line_editor.instance_variable_get(:@cleared)) + assert_byte_pointer_size('ab') + assert_cursor(2) + assert_cursor_max(3) + input_keys("\C-l", false) + assert(@line_editor.instance_variable_get(:@cleared)) + assert_byte_pointer_size('ab') + assert_cursor(2) + assert_cursor_max(3) + assert_line('abc') + end + + def test_em_next_word + assert_byte_pointer_size('') + assert_cursor(0) + input_keys('abc def{bbb}ccc') + input_keys("\C-a\M-F", false) + assert_byte_pointer_size('abc') + assert_cursor(3) + input_keys("\M-F", false) + assert_byte_pointer_size('abc def') + assert_cursor(7) + input_keys("\M-F", false) + assert_byte_pointer_size('abc def{bbb') + assert_cursor(11) + input_keys("\M-F", false) + assert_byte_pointer_size('abc def{bbb}ccc') + assert_cursor(15) + input_keys("\M-F", false) + assert_byte_pointer_size('abc def{bbb}ccc') + assert_cursor(15) + end + + def test_em_next_word_for_mbchar + assert_cursor(0) + input_keys('あいう かきく{さしす}たちつ') + input_keys("\C-a\M-F", false) + assert_byte_pointer_size('あいう') + assert_cursor(6) + input_keys("\M-F", false) + assert_byte_pointer_size('あいう かきく') + assert_cursor(13) + input_keys("\M-F", false) + assert_byte_pointer_size('あいう かきく{さしす') + assert_cursor(20) + input_keys("\M-F", false) + assert_byte_pointer_size('あいう かきく{さしす}たちつ') + assert_cursor(27) + input_keys("\M-F", false) + assert_byte_pointer_size('あいう かきく{さしす}たちつ') + assert_cursor(27) + end + + def test_em_next_word_for_mbchar_by_plural_code_points + assert_cursor(0) + input_keys("あいう か\u3099き\u3099く\u3099{さしす}たちつ") + input_keys("\C-a\M-F", false) + assert_byte_pointer_size("あいう") + assert_cursor(6) + input_keys("\M-F", false) + assert_byte_pointer_size("あいう か\u3099き\u3099く\u3099") + assert_cursor(13) + input_keys("\M-F", false) + assert_byte_pointer_size("あいう か\u3099き\u3099く\u3099{さしす") + assert_cursor(20) + input_keys("\M-F", false) + assert_byte_pointer_size("あいう か\u3099き\u3099く\u3099{さしす}たちつ") + assert_cursor(27) + input_keys("\M-F", false) + assert_byte_pointer_size("あいう か\u3099き\u3099く\u3099{さしす}たちつ") + assert_cursor(27) + end + + def test_em_prev_word + input_keys('abc def{bbb}ccc') + assert_byte_pointer_size('abc def{bbb}ccc') + assert_cursor(15) + input_keys("\M-B", false) + assert_byte_pointer_size('abc def{bbb}') + assert_cursor(12) + input_keys("\M-B", false) + assert_byte_pointer_size('abc def{') + assert_cursor(8) + input_keys("\M-B", false) + assert_byte_pointer_size('abc ') + assert_cursor(4) + input_keys("\M-B", false) + assert_byte_pointer_size('') + assert_cursor(0) + input_keys("\M-B", false) + assert_byte_pointer_size('') + assert_cursor(0) + end + + def test_em_prev_word_for_mbchar + input_keys('あいう かきく{さしす}たちつ') + assert_byte_pointer_size('あいう かきく{さしす}たちつ') + assert_cursor(27) + input_keys("\M-B", false) + assert_byte_pointer_size('あいう かきく{さしす}') + assert_cursor(21) + input_keys("\M-B", false) + assert_byte_pointer_size('あいう かきく{') + assert_cursor(14) + input_keys("\M-B", false) + assert_byte_pointer_size('あいう ') + assert_cursor(7) + input_keys("\M-B", false) + assert_byte_pointer_size('') + assert_cursor(0) + input_keys("\M-B", false) + assert_byte_pointer_size('') + assert_cursor(0) + end + + def test_em_prev_word_for_mbchar_by_plural_code_points + input_keys("あいう か\u3099き\u3099く\u3099{さしす}たちつ") + assert_byte_pointer_size("あいう か\u3099き\u3099く\u3099{さしす}たちつ") + assert_cursor(27) + input_keys("\M-B", false) + assert_byte_pointer_size("あいう か\u3099き\u3099く\u3099{さしす}") + assert_cursor(21) + input_keys("\M-B", false) + assert_byte_pointer_size("あいう か\u3099き\u3099く\u3099{") + assert_cursor(14) + input_keys("\M-B", false) + assert_byte_pointer_size('あいう ') + assert_cursor(7) + input_keys("\M-B", false) + assert_byte_pointer_size('') + assert_cursor(0) + input_keys("\M-B", false) + assert_byte_pointer_size('') + assert_cursor(0) + end + + def test_em_delete_next_word + input_keys('abc def{bbb}ccc') + input_keys("\C-a", false) + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(15) + input_keys("\M-d", false) + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(12) + assert_line(' def{bbb}ccc') + input_keys("\M-d", false) + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(8) + assert_line('{bbb}ccc') + input_keys("\M-d", false) + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(4) + assert_line('}ccc') + input_keys("\M-d", false) + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(0) + assert_line('') + end + + def test_em_delete_next_word_for_mbchar + input_keys('あいう かきく{さしす}たちつ') + input_keys("\C-a", false) + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(27) + input_keys("\M-d", false) + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(21) + assert_line(' かきく{さしす}たちつ') + input_keys("\M-d", false) + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(14) + assert_line('{さしす}たちつ') + input_keys("\M-d", false) + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(7) + assert_line('}たちつ') + input_keys("\M-d", false) + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(0) + assert_line('') + end + + def test_em_delete_next_word_for_mbchar_by_plural_code_points + input_keys("あいう か\u3099き\u3099く\u3099{さしす}たちつ") + input_keys("\C-a", false) + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(27) + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(27) + input_keys("\M-d", false) + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(21) + assert_line(" か\u3099き\u3099く\u3099{さしす}たちつ") + input_keys("\M-d", false) + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(14) + assert_line('{さしす}たちつ') + input_keys("\M-d", false) + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(7) + assert_line('}たちつ') + input_keys("\M-d", false) + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(0) + assert_line('') + end + + def test_ed_delete_prev_word + input_keys('abc def{bbb}ccc') + assert_byte_pointer_size('abc def{bbb}ccc') + assert_cursor(15) + assert_cursor_max(15) + input_keys("\M-\C-H", false) + assert_byte_pointer_size('abc def{bbb}') + assert_cursor(12) + assert_cursor_max(12) + assert_line('abc def{bbb}') + input_keys("\M-\C-H", false) + assert_byte_pointer_size('abc def{') + assert_cursor(8) + assert_cursor_max(8) + assert_line('abc def{') + input_keys("\M-\C-H", false) + assert_byte_pointer_size('abc ') + assert_cursor(4) + assert_cursor_max(4) + assert_line('abc ') + input_keys("\M-\C-H", false) + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(0) + assert_line('') + end + + def test_ed_delete_prev_word_for_mbchar + input_keys('あいう かきく{さしす}たちつ') + assert_byte_pointer_size('あいう かきく{さしす}たちつ') + assert_cursor(27) + assert_cursor_max(27) + input_keys("\M-\C-H", false) + assert_byte_pointer_size('あいう かきく{さしす}') + assert_cursor(21) + assert_cursor_max(21) + assert_line('あいう かきく{さしす}') + input_keys("\M-\C-H", false) + assert_byte_pointer_size('あいう かきく{') + assert_cursor(14) + assert_cursor_max(14) + assert_line('あいう かきく{') + input_keys("\M-\C-H", false) + assert_byte_pointer_size('あいう ') + assert_cursor(7) + assert_cursor_max(7) + assert_line('あいう ') + input_keys("\M-\C-H", false) + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(0) + assert_line('') + end + + def test_ed_delete_prev_word_for_mbchar_by_plural_code_points + input_keys("あいう か\u3099き\u3099く\u3099{さしす}たちつ") + assert_byte_pointer_size("あいう か\u3099き\u3099く\u3099{さしす}たちつ") + assert_cursor(27) + assert_cursor_max(27) + input_keys("\M-\C-H", false) + assert_byte_pointer_size("あいう か\u3099き\u3099く\u3099{さしす}") + assert_cursor(21) + assert_cursor_max(21) + assert_line("あいう か\u3099き\u3099く\u3099{さしす}") + input_keys("\M-\C-H", false) + assert_byte_pointer_size("あいう か\u3099き\u3099く\u3099{") + assert_cursor(14) + assert_cursor_max(14) + assert_line("あいう か\u3099き\u3099く\u3099{") + input_keys("\M-\C-H", false) + assert_byte_pointer_size("あいう ") + assert_cursor(7) + assert_cursor_max(7) + assert_line('あいう ') + input_keys("\M-\C-H", false) + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(0) + assert_line('') + end + + def test_ed_transpose_chars + input_keys('abc') + input_keys("\C-a", false) + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(3) + input_keys("\C-t", false) + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(3) + assert_line('abc') + input_keys("\C-f\C-t", false) + assert_byte_pointer_size('ba') + assert_cursor(2) + assert_cursor_max(3) + assert_line('bac') + input_keys("\C-t", false) + assert_byte_pointer_size('bca') + assert_cursor(3) + assert_cursor_max(3) + assert_line('bca') + input_keys("\C-t", false) + assert_byte_pointer_size('bac') + assert_cursor(3) + assert_cursor_max(3) + assert_line('bac') + end + + def test_ed_transpose_chars_for_mbchar + input_keys('あかさ') + input_keys("\C-a", false) + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(6) + input_keys("\C-t", false) + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(6) + assert_line('あかさ') + input_keys("\C-f\C-t", false) + assert_byte_pointer_size('かあ') + assert_cursor(4) + assert_cursor_max(6) + assert_line('かあさ') + input_keys("\C-t", false) + assert_byte_pointer_size('かさあ') + assert_cursor(6) + assert_cursor_max(6) + assert_line('かさあ') + input_keys("\C-t", false) + assert_byte_pointer_size('かあさ') + assert_cursor(6) + assert_cursor_max(6) + assert_line('かあさ') + end + + def test_ed_transpose_chars_for_mbchar_by_plural_code_points + input_keys("あか\u3099さ") + input_keys("\C-a", false) + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(6) + input_keys("\C-t", false) + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(6) + assert_line("あか\u3099さ") + input_keys("\C-f\C-t", false) + assert_byte_pointer_size("か\u3099あ") + assert_cursor(4) + assert_cursor_max(6) + assert_line("か\u3099あさ") + input_keys("\C-t", false) + assert_byte_pointer_size("か\u3099さあ") + assert_cursor(6) + assert_cursor_max(6) + assert_line("か\u3099さあ") + input_keys("\C-t", false) + assert_byte_pointer_size("か\u3099あさ") + assert_cursor(6) + assert_cursor_max(6) + assert_line("か\u3099あさ") + end + + def test_ed_digit + input_keys('0123') + assert_byte_pointer_size('0123') + assert_cursor(4) + assert_cursor_max(4) + assert_line('0123') + end + + def test_ed_next_and_prev_char + input_keys('abc') + assert_byte_pointer_size('abc') + assert_cursor(3) + assert_cursor_max(3) + input_keys("\C-b", false) + assert_byte_pointer_size('ab') + assert_cursor(2) + assert_cursor_max(3) + input_keys("\C-b", false) + assert_byte_pointer_size('a') + assert_cursor(1) + assert_cursor_max(3) + input_keys("\C-b", false) + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(3) + input_keys("\C-b", false) + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(3) + input_keys("\C-f", false) + assert_byte_pointer_size('a') + assert_cursor(1) + assert_cursor_max(3) + input_keys("\C-f", false) + assert_byte_pointer_size('ab') + assert_cursor(2) + assert_cursor_max(3) + input_keys("\C-f", false) + assert_byte_pointer_size('abc') + assert_cursor(3) + assert_cursor_max(3) + input_keys("\C-f", false) + assert_byte_pointer_size('abc') + assert_cursor(3) + assert_cursor_max(3) + end + + def test_ed_next_and_prev_char_for_mbchar + input_keys('あいう') + assert_byte_pointer_size('あいう') + assert_cursor(6) + assert_cursor_max(6) + input_keys("\C-b", false) + assert_byte_pointer_size('あい') + assert_cursor(4) + assert_cursor_max(6) + input_keys("\C-b", false) + assert_byte_pointer_size('あ') + assert_cursor(2) + assert_cursor_max(6) + input_keys("\C-b", false) + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(6) + input_keys("\C-b", false) + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(6) + input_keys("\C-f", false) + assert_byte_pointer_size('あ') + assert_cursor(2) + assert_cursor_max(6) + input_keys("\C-f", false) + assert_byte_pointer_size('あい') + assert_cursor(4) + assert_cursor_max(6) + input_keys("\C-f", false) + assert_byte_pointer_size('あいう') + assert_cursor(6) + assert_cursor_max(6) + input_keys("\C-f", false) + assert_byte_pointer_size('あいう') + assert_cursor(6) + assert_cursor_max(6) + end + + def test_ed_next_and_prev_char_for_mbchar_by_plural_code_points + input_keys("か\u3099き\u3099く\u3099") + assert_byte_pointer_size("か\u3099き\u3099く\u3099") + assert_cursor(6) + assert_cursor_max(6) + input_keys("\C-b", false) + assert_byte_pointer_size("か\u3099き\u3099") + assert_cursor(4) + assert_cursor_max(6) + input_keys("\C-b", false) + assert_byte_pointer_size("か\u3099") + assert_cursor(2) + assert_cursor_max(6) + input_keys("\C-b", false) + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(6) + input_keys("\C-b", false) + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(6) + input_keys("\C-f", false) + assert_byte_pointer_size("か\u3099") + assert_cursor(2) + assert_cursor_max(6) + input_keys("\C-f", false) + assert_byte_pointer_size("か\u3099き\u3099") + assert_cursor(4) + assert_cursor_max(6) + input_keys("\C-f", false) + assert_byte_pointer_size("か\u3099き\u3099く\u3099") + assert_cursor(6) + assert_cursor_max(6) + input_keys("\C-f", false) + assert_byte_pointer_size("か\u3099き\u3099く\u3099") + assert_cursor(6) + assert_cursor_max(6) + end + + def test_em_capitol_case + input_keys('abc def{bbb}ccc') + input_keys("\C-a\M-c", false) + assert_byte_pointer_size('Abc') + assert_cursor(3) + assert_cursor_max(15) + assert_line('Abc def{bbb}ccc') + input_keys("\M-c", false) + assert_byte_pointer_size('Abc Def') + assert_cursor(7) + assert_cursor_max(15) + assert_line('Abc Def{bbb}ccc') + input_keys("\M-c", false) + assert_byte_pointer_size('Abc Def{Bbb') + assert_cursor(11) + assert_cursor_max(15) + assert_line('Abc Def{Bbb}ccc') + input_keys("\M-c", false) + assert_byte_pointer_size('Abc Def{Bbb}Ccc') + assert_cursor(15) + assert_cursor_max(15) + assert_line('Abc Def{Bbb}Ccc') + end + + def test_em_capitol_case_with_complex_example + input_keys('{}#* AaA!!!cCc ') + input_keys("\C-a\M-c", false) + assert_byte_pointer_size('{}#* Aaa') + assert_cursor(11) + assert_cursor_max(20) + assert_line('{}#* Aaa!!!cCc ') + input_keys("\M-c", false) + assert_byte_pointer_size('{}#* Aaa!!!Ccc') + assert_cursor(17) + assert_cursor_max(20) + assert_line('{}#* Aaa!!!Ccc ') + input_keys("\M-c", false) + assert_byte_pointer_size('{}#* Aaa!!!Ccc ') + assert_cursor(20) + assert_cursor_max(20) + assert_line('{}#* Aaa!!!Ccc ') + end + + def test_em_lower_case + input_keys('AbC def{bBb}CCC') + input_keys("\C-a\M-l", false) + assert_byte_pointer_size('abc') + assert_cursor(3) + assert_cursor_max(15) + assert_line('abc def{bBb}CCC') + input_keys("\M-l", false) + assert_byte_pointer_size('abc def') + assert_cursor(7) + assert_cursor_max(15) + assert_line('abc def{bBb}CCC') + input_keys("\M-l", false) + assert_byte_pointer_size('abc def{bbb') + assert_cursor(11) + assert_cursor_max(15) + assert_line('abc def{bbb}CCC') + input_keys("\M-l", false) + assert_byte_pointer_size('abc def{bbb}ccc') + assert_cursor(15) + assert_cursor_max(15) + assert_line('abc def{bbb}ccc') + end + + def test_em_lower_case_with_complex_example + input_keys('{}#* AaA!!!cCc ') + input_keys("\C-a\M-l", false) + assert_byte_pointer_size('{}#* aaa') + assert_cursor(11) + assert_cursor_max(20) + assert_line('{}#* aaa!!!cCc ') + input_keys("\M-l", false) + assert_byte_pointer_size('{}#* aaa!!!ccc') + assert_cursor(17) + assert_cursor_max(20) + assert_line('{}#* aaa!!!ccc ') + input_keys("\M-l", false) + assert_byte_pointer_size('{}#* aaa!!!ccc ') + assert_cursor(20) + assert_cursor_max(20) + assert_line('{}#* aaa!!!ccc ') + end + + def test_em_upper_case + input_keys('AbC def{bBb}CCC') + input_keys("\C-a\M-u", false) + assert_byte_pointer_size('ABC') + assert_cursor(3) + assert_cursor_max(15) + assert_line('ABC def{bBb}CCC') + input_keys("\M-u", false) + assert_byte_pointer_size('ABC DEF') + assert_cursor(7) + assert_cursor_max(15) + assert_line('ABC DEF{bBb}CCC') + input_keys("\M-u", false) + assert_byte_pointer_size('ABC DEF{BBB') + assert_cursor(11) + assert_cursor_max(15) + assert_line('ABC DEF{BBB}CCC') + input_keys("\M-u", false) + assert_byte_pointer_size('ABC DEF{BBB}CCC') + assert_cursor(15) + assert_cursor_max(15) + assert_line('ABC DEF{BBB}CCC') + end + + def test_em_upper_case_with_complex_example + input_keys('{}#* AaA!!!cCc ') + input_keys("\C-a\M-u", false) + assert_byte_pointer_size('{}#* AAA') + assert_cursor(11) + assert_cursor_max(20) + assert_line('{}#* AAA!!!cCc ') + input_keys("\M-u", false) + assert_byte_pointer_size('{}#* AAA!!!CCC') + assert_cursor(17) + assert_cursor_max(20) + assert_line('{}#* AAA!!!CCC ') + input_keys("\M-u", false) + assert_byte_pointer_size('{}#* AAA!!!CCC ') + assert_cursor(20) + assert_cursor_max(20) + assert_line('{}#* AAA!!!CCC ') + end + + def test_completion + @line_editor.completion_proc = proc { |word| + %w{ + foo_foo + foo_bar + foo_baz + qux + } + } + input_keys('fo') + assert_byte_pointer_size('fo') + assert_cursor(2) + assert_cursor_max(2) + assert_line('fo') + assert_equal(nil, @line_editor.instance_variable_get(:@menu_info)) + input_keys("\C-i", false) + assert_byte_pointer_size('foo_') + assert_cursor(4) + assert_cursor_max(4) + assert_line('foo_') + assert_equal(nil, @line_editor.instance_variable_get(:@menu_info)) + input_keys("\C-i", false) + assert_byte_pointer_size('foo_') + assert_cursor(4) + assert_cursor_max(4) + assert_line('foo_') + assert_equal(%w{foo_foo foo_bar foo_baz}, @line_editor.instance_variable_get(:@menu_info).list) + input_keys('a') + input_keys("\C-i", false) + assert_byte_pointer_size('foo_a') + assert_cursor(5) + assert_cursor_max(5) + assert_line('foo_a') + input_keys("\C-h", false) + input_keys('b') + input_keys("\C-i", false) + assert_byte_pointer_size('foo_ba') + assert_cursor(6) + assert_cursor_max(6) + assert_line('foo_ba') + end + + def test_completion_in_middle_of_line + @line_editor.completion_proc = proc { |word| + %w{ + foo_foo + foo_bar + foo_baz + qux + } + } + input_keys('abcde fo ABCDE') + assert_line('abcde fo ABCDE') + input_keys("\C-b" * 6 + "\C-i", false) + assert_byte_pointer_size('abcde foo_') + assert_cursor(10) + assert_cursor_max(16) + assert_line('abcde foo_ ABCDE') + input_keys("\C-b" * 2 + "\C-i", false) + assert_byte_pointer_size('abcde foo_') + assert_cursor(10) + assert_cursor_max(18) + assert_line('abcde foo_o_ ABCDE') + end + + def test_em_kill_region + input_keys('abc def{bbb}ccc ddd ') + assert_byte_pointer_size('abc def{bbb}ccc ddd ') + assert_cursor(26) + assert_cursor_max(26) + assert_line('abc def{bbb}ccc ddd ') + input_keys("\C-w", false) + assert_byte_pointer_size('abc def{bbb}ccc ') + assert_cursor(20) + assert_cursor_max(20) + assert_line('abc def{bbb}ccc ') + input_keys("\C-w", false) + assert_byte_pointer_size('abc ') + assert_cursor(6) + assert_cursor_max(6) + assert_line('abc ') + input_keys("\C-w", false) + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(0) + assert_line('') + input_keys("\C-w", false) + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(0) + assert_line('') + end + + def test_em_kill_region_mbchar + input_keys('あ い う{う}う ') + assert_byte_pointer_size('あ い う{う}う ') + assert_cursor(21) + assert_cursor_max(21) + assert_line('あ い う{う}う ') + input_keys("\C-w", false) + assert_byte_pointer_size('あ い ') + assert_cursor(10) + assert_cursor_max(10) + assert_line('あ い ') + input_keys("\C-w", false) + assert_byte_pointer_size('あ ') + assert_cursor(5) + assert_cursor_max(5) + assert_line('あ ') + input_keys("\C-w", false) + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(0) + assert_line('') + end +end diff --git a/test/reline/test_key_stroke.rb b/test/reline/test_key_stroke.rb new file mode 100644 index 00000000000000..b6d5ce415073c3 --- /dev/null +++ b/test/reline/test_key_stroke.rb @@ -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 diff --git a/test/reline/test_key_vi_emacs.rb b/test/reline/test_key_vi_emacs.rb new file mode 100644 index 00000000000000..63618d5fb35957 --- /dev/null +++ b/test/reline/test_key_vi_emacs.rb @@ -0,0 +1,1026 @@ +require_relative 'helper' + +class Reline::KeyActor::ViInsert::Test < Reline::TestCase + def setup + @prompt = '> ' + @config = Reline::Config.new + @config.read_lines(<<~LINES.split(/(?<=\n)/)) + set editing-mode vi + LINES + @line_editor = Reline::LineEditor.new( + @config, @prompt, + (RELINE_TEST_ENCODING rescue Encoding.default_external)) + @line_editor.retrieve_completion_block = Reline.method(:retrieve_completion_block) + end + + def test_vi_command_mode + input_keys("\C-[") + assert_instance_of(Reline::KeyActor::ViCommand, @config.editing_mode) + end + + def test_vi_command_mode_with_input + input_keys("abc\C-[") + assert_instance_of(Reline::KeyActor::ViCommand, @config.editing_mode) + assert_line('abc') + end + + def test_ed_insert_one + input_keys('a') + assert_line('a') + assert_byte_pointer_size('a') + assert_cursor(1) + assert_cursor_max(1) + end + + def test_ed_insert_two + input_keys('ab') + assert_line('ab') + assert_byte_pointer_size('ab') + assert_cursor(2) + assert_cursor_max(2) + end + + def test_ed_insert_mbchar_one + input_keys('か') + assert_line('か') + assert_byte_pointer_size('か') + assert_cursor(2) + assert_cursor_max(2) + end + + def test_ed_insert_mbchar_two + input_keys('かき') + assert_line('かき') + assert_byte_pointer_size('かき') + assert_cursor(4) + assert_cursor_max(4) + end + + def test_ed_insert_for_mbchar_by_plural_code_points + input_keys("か\u3099") + assert_line("か\u3099") + assert_byte_pointer_size("か\u3099") + assert_cursor(2) + assert_cursor_max(2) + end + + def test_ed_insert_for_plural_mbchar_by_plural_code_points + input_keys("か\u3099き\u3099") + assert_line("か\u3099き\u3099") + assert_byte_pointer_size("か\u3099き\u3099") + assert_cursor(4) + assert_cursor_max(4) + end + + def test_ed_next_char + input_keys("abcdef\C-[0") + assert_line('abcdef') + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(6) + input_keys('l') + assert_line('abcdef') + assert_byte_pointer_size('a') + assert_cursor(1) + assert_cursor_max(6) + input_keys('2l') + assert_line('abcdef') + assert_byte_pointer_size('abc') + assert_cursor(3) + assert_cursor_max(6) + end + + def test_ed_prev_char + input_keys("abcdef\C-[") + assert_line('abcdef') + assert_byte_pointer_size('abcde') + assert_cursor(5) + assert_cursor_max(6) + input_keys('h') + assert_line('abcdef') + assert_byte_pointer_size('abcd') + assert_cursor(4) + assert_cursor_max(6) + input_keys('2h') + assert_line('abcdef') + assert_byte_pointer_size('ab') + assert_cursor(2) + assert_cursor_max(6) + end + + def test_history + Reline::HISTORY.concat(%w{abc 123 AAA}) + input_keys("\C-[") + assert_line('') + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(0) + input_keys('k') + assert_line('AAA') + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(3) + input_keys('2k') + assert_line('abc') + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(3) + input_keys('j') + assert_line('123') + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(3) + input_keys('2j') + assert_line('') + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(0) + end + + def test_vi_paste_prev + input_keys("abcde\C-[3h") + assert_line('abcde') + assert_byte_pointer_size('a') + assert_cursor(1) + assert_cursor_max(5) + input_keys('P') + assert_line('abcde') + assert_byte_pointer_size('a') + assert_cursor(1) + assert_cursor_max(5) + input_keys('d$') + assert_line('a') + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(1) + input_keys('P') + assert_line('bcdea') + assert_byte_pointer_size('bcd') + assert_cursor(3) + assert_cursor_max(5) + input_keys('2P') + assert_line('bcdbcdbcdeeea') + assert_byte_pointer_size('bcdbcdbcd') + assert_cursor(9) + assert_cursor_max(13) + end + + def test_vi_paste_next + input_keys("abcde\C-[3h") + assert_line('abcde') + assert_byte_pointer_size('a') + assert_cursor(1) + assert_cursor_max(5) + input_keys('p') + assert_line('abcde') + assert_byte_pointer_size('a') + assert_cursor(1) + assert_cursor_max(5) + input_keys('d$') + assert_line('a') + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(1) + input_keys('p') + assert_line('abcde') + assert_byte_pointer_size('abcd') + assert_cursor(4) + assert_cursor_max(5) + input_keys('2p') + assert_line('abcdebcdebcde') + assert_byte_pointer_size('abcdebcdebcd') + assert_cursor(12) + assert_cursor_max(13) + end + + def test_vi_paste_prev_for_mbchar + input_keys("あいうえお\C-[3h") + assert_line('あいうえお') + assert_byte_pointer_size('あ') + assert_cursor(2) + assert_cursor_max(10) + input_keys('P') + assert_line('あいうえお') + assert_byte_pointer_size('あ') + assert_cursor(2) + assert_cursor_max(10) + input_keys('d$') + assert_line('あ') + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(2) + input_keys('P') + assert_line('いうえおあ') + assert_byte_pointer_size('いうえ') + assert_cursor(6) + assert_cursor_max(10) + input_keys('2P') + assert_line('いうえいうえいうえおおおあ') + assert_byte_pointer_size('いうえいうえいうえ') + assert_cursor(18) + assert_cursor_max(26) + end + + def test_vi_paste_next_for_mbchar + input_keys("あいうえお\C-[3h") + assert_line('あいうえお') + assert_byte_pointer_size('あ') + assert_cursor(2) + assert_cursor_max(10) + input_keys('p') + assert_line('あいうえお') + assert_byte_pointer_size('あ') + assert_cursor(2) + assert_cursor_max(10) + input_keys('d$') + assert_line('あ') + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(2) + input_keys('p') + assert_line('あいうえお') + assert_byte_pointer_size('あいうえ') + assert_cursor(8) + assert_cursor_max(10) + input_keys('2p') + assert_line('あいうえおいうえおいうえお') + assert_byte_pointer_size('あいうえおいうえおいうえ') + assert_cursor(24) + assert_cursor_max(26) + end + + def test_vi_paste_prev_for_mbchar_by_plural_code_points + input_keys("か\u3099き\u3099く\u3099け\u3099こ\u3099\C-[3h") + assert_line("か\u3099き\u3099く\u3099け\u3099こ\u3099") + assert_byte_pointer_size("か\u3099") + assert_cursor(2) + assert_cursor_max(10) + input_keys('P') + assert_line("か\u3099き\u3099く\u3099け\u3099こ\u3099") + assert_byte_pointer_size("か\u3099") + assert_cursor(2) + assert_cursor_max(10) + input_keys('d$') + assert_line("か\u3099") + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(2) + input_keys('P') + assert_line("き\u3099く\u3099け\u3099こ\u3099か\u3099") + assert_byte_pointer_size("き\u3099く\u3099け\u3099") + assert_cursor(6) + assert_cursor_max(10) + input_keys('2P') + assert_line("き\u3099く\u3099け\u3099き\u3099く\u3099け\u3099き\u3099く\u3099け\u3099こ\u3099こ\u3099こ\u3099か\u3099") + assert_byte_pointer_size("き\u3099く\u3099け\u3099き\u3099く\u3099け\u3099き\u3099く\u3099け\u3099") + assert_cursor(18) + assert_cursor_max(26) + end + + def test_vi_paste_next_for_mbchar_by_plural_code_points + input_keys("か\u3099き\u3099く\u3099け\u3099こ\u3099\C-[3h") + assert_line("か\u3099き\u3099く\u3099け\u3099こ\u3099") + assert_byte_pointer_size("か\u3099") + assert_cursor(2) + assert_cursor_max(10) + input_keys('p') + assert_line("か\u3099き\u3099く\u3099け\u3099こ\u3099") + assert_byte_pointer_size("か\u3099") + assert_cursor(2) + assert_cursor_max(10) + input_keys('d$') + assert_line("か\u3099") + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(2) + input_keys('p') + assert_line("か\u3099き\u3099く\u3099け\u3099こ\u3099") + assert_byte_pointer_size("か\u3099き\u3099く\u3099け\u3099") + assert_cursor(8) + assert_cursor_max(10) + input_keys('2p') + assert_line("か\u3099き\u3099く\u3099け\u3099こ\u3099き\u3099く\u3099け\u3099こ\u3099き\u3099く\u3099け\u3099こ\u3099") + assert_byte_pointer_size("か\u3099き\u3099く\u3099け\u3099こ\u3099き\u3099く\u3099け\u3099こ\u3099き\u3099く\u3099け\u3099") + assert_cursor(24) + assert_cursor_max(26) + end + + def test_vi_prev_next_word + input_keys("aaa b{b}b ccc\C-[0") + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(13) + input_keys('w') + assert_byte_pointer_size('aaa ') + assert_cursor(4) + assert_cursor_max(13) + input_keys('w') + assert_byte_pointer_size('aaa b') + assert_cursor(5) + assert_cursor_max(13) + input_keys('w') + assert_byte_pointer_size('aaa b{') + assert_cursor(6) + assert_cursor_max(13) + input_keys('w') + assert_byte_pointer_size('aaa b{b') + assert_cursor(7) + assert_cursor_max(13) + input_keys('w') + assert_byte_pointer_size('aaa b{b}') + assert_cursor(8) + assert_cursor_max(13) + input_keys('w') + assert_byte_pointer_size('aaa b{b}b ') + assert_cursor(10) + assert_cursor_max(13) + input_keys('w') + assert_byte_pointer_size('aaa b{b}b cc') + assert_cursor(12) + assert_cursor_max(13) + input_keys('b') + assert_byte_pointer_size('aaa b{b}b ') + assert_cursor(10) + assert_cursor_max(13) + input_keys('b') + assert_byte_pointer_size('aaa b{b}') + assert_cursor(8) + assert_cursor_max(13) + input_keys('b') + assert_byte_pointer_size('aaa b{b') + assert_cursor(7) + assert_cursor_max(13) + input_keys('b') + assert_byte_pointer_size('aaa b{') + assert_cursor(6) + assert_cursor_max(13) + input_keys('b') + assert_byte_pointer_size('aaa b') + assert_cursor(5) + assert_cursor_max(13) + input_keys('b') + assert_byte_pointer_size('aaa ') + assert_cursor(4) + assert_cursor_max(13) + input_keys('b') + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(13) + input_keys('3w') + assert_byte_pointer_size('aaa b{') + assert_cursor(6) + assert_cursor_max(13) + input_keys('3w') + assert_byte_pointer_size('aaa b{b}b ') + assert_cursor(10) + assert_cursor_max(13) + input_keys('3w') + assert_byte_pointer_size('aaa b{b}b cc') + assert_cursor(12) + assert_cursor_max(13) + input_keys('3b') + assert_byte_pointer_size('aaa b{b') + assert_cursor(7) + assert_cursor_max(13) + input_keys('3b') + assert_byte_pointer_size('aaa ') + assert_cursor(4) + assert_cursor_max(13) + input_keys('3b') + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(13) + end + + def test_vi_end_word + input_keys("aaa b{b}}}b ccc\C-[0") + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(19) + input_keys('e') + assert_byte_pointer_size('aa') + assert_cursor(2) + assert_cursor_max(19) + input_keys('e') + assert_byte_pointer_size('aaa ') + assert_cursor(6) + assert_cursor_max(19) + input_keys('e') + assert_byte_pointer_size('aaa b') + assert_cursor(7) + assert_cursor_max(19) + input_keys('e') + assert_byte_pointer_size('aaa b{') + assert_cursor(8) + assert_cursor_max(19) + input_keys('e') + assert_byte_pointer_size('aaa b{b}}') + assert_cursor(11) + assert_cursor_max(19) + input_keys('e') + assert_byte_pointer_size('aaa b{b}}}') + assert_cursor(12) + assert_cursor_max(19) + input_keys('e') + assert_byte_pointer_size('aaa b{b}}}b cc') + assert_cursor(18) + assert_cursor_max(19) + input_keys('e') + assert_byte_pointer_size('aaa b{b}}}b cc') + assert_cursor(18) + assert_cursor_max(19) + input_keys('03e') + assert_byte_pointer_size('aaa b') + assert_cursor(7) + assert_cursor_max(19) + input_keys('3e') + assert_byte_pointer_size('aaa b{b}}}') + assert_cursor(12) + assert_cursor_max(19) + input_keys('3e') + assert_byte_pointer_size('aaa b{b}}}b cc') + assert_cursor(18) + assert_cursor_max(19) + end + + def test_vi_prev_next_big_word + input_keys("aaa b{b}b ccc\C-[0") + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(13) + input_keys('W') + assert_byte_pointer_size('aaa ') + assert_cursor(4) + assert_cursor_max(13) + input_keys('W') + assert_byte_pointer_size('aaa b{b}b ') + assert_cursor(10) + assert_cursor_max(13) + input_keys('W') + assert_byte_pointer_size('aaa b{b}b cc') + assert_cursor(12) + assert_cursor_max(13) + input_keys('B') + assert_byte_pointer_size('aaa b{b}b ') + assert_cursor(10) + assert_cursor_max(13) + input_keys('B') + assert_byte_pointer_size('aaa ') + assert_cursor(4) + assert_cursor_max(13) + input_keys('B') + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(13) + input_keys('2W') + assert_byte_pointer_size('aaa b{b}b ') + assert_cursor(10) + assert_cursor_max(13) + input_keys('2W') + assert_byte_pointer_size('aaa b{b}b cc') + assert_cursor(12) + assert_cursor_max(13) + input_keys('2B') + assert_byte_pointer_size('aaa ') + assert_cursor(4) + assert_cursor_max(13) + input_keys('2B') + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(13) + end + + def test_vi_end_big_word + input_keys("aaa b{b}}}b ccc\C-[0") + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(19) + input_keys('E') + assert_byte_pointer_size('aa') + assert_cursor(2) + assert_cursor_max(19) + input_keys('E') + assert_byte_pointer_size('aaa b{b}}}') + assert_cursor(12) + assert_cursor_max(19) + input_keys('E') + assert_byte_pointer_size('aaa b{b}}}b cc') + assert_cursor(18) + assert_cursor_max(19) + input_keys('E') + assert_byte_pointer_size('aaa b{b}}}b cc') + assert_cursor(18) + assert_cursor_max(19) + end + + def test_ed_quoted_insert + input_keys("ab\C-v\C-acd") + assert_line("ab\C-acd") + assert_byte_pointer_size("ab\C-acd") + assert_cursor(6) + assert_cursor_max(6) + end + + def test_ed_quoted_insert_with_vi_arg + input_keys("ab\C-[3\C-v\C-aacd") + assert_line("a\C-a\C-a\C-abcd") + assert_byte_pointer_size("a\C-a\C-a\C-abcd") + assert_cursor(10) + assert_cursor_max(10) + end + + def test_vi_replace_char + input_keys("abcdef\C-[03l") + assert_line('abcdef') + assert_byte_pointer_size('abc') + assert_cursor(3) + assert_cursor_max(6) + input_keys('rz') + assert_line('abczef') + assert_byte_pointer_size('abc') + assert_cursor(3) + assert_cursor_max(6) + input_keys('2rx') + assert_line('abcxxf') + assert_byte_pointer_size('abcxx') + assert_cursor(5) + assert_cursor_max(6) + end + + def test_vi_next_char + input_keys("abcdef\C-[0") + assert_line('abcdef') + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(6) + input_keys('fz') + assert_line('abcdef') + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(6) + input_keys('fe') + assert_line('abcdef') + assert_byte_pointer_size('abcd') + assert_cursor(4) + assert_cursor_max(6) + end + + def test_vi_delete_next_char + input_keys("abc\C-[h") + assert_byte_pointer_size('a') + assert_cursor(1) + assert_cursor_max(3) + assert_line('abc') + input_keys('x') + assert_byte_pointer_size('a') + assert_cursor(1) + assert_cursor_max(2) + assert_line('ac') + input_keys('x') + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(1) + assert_line('a') + input_keys('x') + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(0) + assert_line('') + input_keys('x') + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(0) + assert_line('') + end + + def test_vi_delete_next_char_for_mbchar + input_keys("あいう\C-[h") + assert_byte_pointer_size('あ') + assert_cursor(2) + assert_cursor_max(6) + assert_line('あいう') + input_keys('x') + assert_byte_pointer_size('あ') + assert_cursor(2) + assert_cursor_max(4) + assert_line('あう') + input_keys('x') + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(2) + assert_line('あ') + input_keys('x') + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(0) + assert_line('') + input_keys('x') + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(0) + assert_line('') + end + + def test_vi_delete_next_char_for_mbchar_by_plural_code_points + input_keys("か\u3099き\u3099く\u3099\C-[h") + assert_byte_pointer_size("か\u3099") + assert_cursor(2) + assert_cursor_max(6) + assert_line("か\u3099き\u3099く\u3099") + input_keys('x') + assert_byte_pointer_size("か\u3099") + assert_cursor(2) + assert_cursor_max(4) + assert_line("か\u3099く\u3099") + input_keys('x') + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(2) + assert_line("か\u3099") + input_keys('x') + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(0) + assert_line('') + input_keys('x') + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(0) + assert_line('') + end + + def test_vi_delete_prev_char + input_keys('ab') + assert_byte_pointer_size('ab') + assert_cursor(2) + assert_cursor_max(2) + input_keys("\C-h") + assert_byte_pointer_size('a') + assert_cursor(1) + assert_cursor_max(1) + assert_line('a') + end + + def test_vi_delete_prev_char_for_mbchar + input_keys('かき') + assert_byte_pointer_size('かき') + assert_cursor(4) + assert_cursor_max(4) + input_keys("\C-h") + assert_byte_pointer_size('か') + assert_cursor(2) + assert_cursor_max(2) + assert_line('か') + end + + def test_vi_delete_prev_char_for_mbchar_by_plural_code_points + input_keys("か\u3099き\u3099") + assert_byte_pointer_size("か\u3099き\u3099") + assert_cursor(4) + assert_cursor_max(4) + input_keys("\C-h") + assert_byte_pointer_size("か\u3099") + assert_cursor(2) + assert_cursor_max(2) + assert_line("か\u3099") + end + + def test_ed_delete_prev_char + input_keys("abcdefg\C-[h") + assert_byte_pointer_size('abcde') + assert_cursor(5) + assert_cursor_max(7) + assert_line('abcdefg') + input_keys('X') + assert_byte_pointer_size('abcd') + assert_cursor(4) + assert_cursor_max(6) + assert_line('abcdfg') + input_keys('3X') + assert_byte_pointer_size('a') + assert_cursor(1) + assert_cursor_max(3) + assert_line('afg') + input_keys('p') + assert_byte_pointer_size('abcd') + assert_cursor(4) + assert_cursor_max(6) + assert_line('afbcdg') + end + + def test_ed_delete_prev_word + input_keys('abc def{bbb}ccc') + assert_byte_pointer_size('abc def{bbb}ccc') + assert_cursor(15) + assert_cursor_max(15) + input_keys("\C-w") + assert_byte_pointer_size('abc def{bbb}') + assert_cursor(12) + assert_cursor_max(12) + assert_line('abc def{bbb}') + input_keys("\C-w") + assert_byte_pointer_size('abc def{') + assert_cursor(8) + assert_cursor_max(8) + assert_line('abc def{') + input_keys("\C-w") + assert_byte_pointer_size('abc ') + assert_cursor(4) + assert_cursor_max(4) + assert_line('abc ') + input_keys("\C-w") + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(0) + assert_line('') + end + + def test_ed_delete_prev_word_for_mbchar + input_keys('あいう かきく{さしす}たちつ') + assert_byte_pointer_size('あいう かきく{さしす}たちつ') + assert_cursor(27) + assert_cursor_max(27) + input_keys("\C-w") + assert_byte_pointer_size('あいう かきく{さしす}') + assert_cursor(21) + assert_cursor_max(21) + assert_line('あいう かきく{さしす}') + input_keys("\C-w") + assert_byte_pointer_size('あいう かきく{') + assert_cursor(14) + assert_cursor_max(14) + assert_line('あいう かきく{') + input_keys("\C-w") + assert_byte_pointer_size('あいう ') + assert_cursor(7) + assert_cursor_max(7) + assert_line('あいう ') + input_keys("\C-w") + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(0) + assert_line('') + end + + def test_ed_delete_prev_word_for_mbchar_by_plural_code_points + input_keys("あいう か\u3099き\u3099く\u3099{さしす}たちつ") + assert_byte_pointer_size("あいう か\u3099き\u3099く\u3099{さしす}たちつ") + assert_cursor(27) + assert_cursor_max(27) + input_keys("\C-w") + assert_byte_pointer_size("あいう か\u3099き\u3099く\u3099{さしす}") + assert_cursor(21) + assert_cursor_max(21) + assert_line("あいう か\u3099き\u3099く\u3099{さしす}") + input_keys("\C-w") + assert_byte_pointer_size("あいう か\u3099き\u3099く\u3099{") + assert_cursor(14) + assert_cursor_max(14) + assert_line("あいう か\u3099き\u3099く\u3099{") + input_keys("\C-w") + assert_byte_pointer_size('あいう ') + assert_cursor(7) + assert_cursor_max(7) + assert_line('あいう ') + input_keys("\C-w") + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(0) + assert_line('') + end + + def test_ed_newline_with_cr + input_keys('ab') + assert_byte_pointer_size('ab') + assert_cursor(2) + assert_cursor_max(2) + refute(@line_editor.finished?) + input_keys("\C-m") + assert_line('ab') + assert(@line_editor.finished?) + end + + def test_ed_newline_with_lf + input_keys('ab') + assert_byte_pointer_size('ab') + assert_cursor(2) + assert_cursor_max(2) + refute(@line_editor.finished?) + input_keys("\C-j") + assert_line('ab') + assert(@line_editor.finished?) + end + + def test_vi_list_or_eof + input_keys('a') + assert_byte_pointer_size('a') + assert_cursor(1) + assert_cursor_max(1) + refute(@line_editor.finished?) + input_keys("\C-d") + assert_line('a') + refute(@line_editor.finished?) + input_keys("\C-h\C-d") + assert_line(nil) + assert(@line_editor.finished?) + end + + def test_completion_journey + @line_editor.completion_proc = proc { |word| + %w{ + foo_bar + foo_bar_baz + } + } + input_keys('foo') + assert_byte_pointer_size('foo') + assert_cursor(3) + assert_cursor_max(3) + assert_line('foo') + input_keys("\C-n") + assert_byte_pointer_size('foo') + assert_cursor(3) + assert_cursor_max(3) + assert_line('foo') + input_keys("\C-n") + assert_byte_pointer_size('foo_bar') + assert_cursor(7) + assert_cursor_max(7) + assert_line('foo_bar') + input_keys("\C-n") + assert_byte_pointer_size('foo_bar_baz') + assert_cursor(11) + assert_cursor_max(11) + assert_line('foo_bar_baz') + input_keys("\C-n") + assert_byte_pointer_size('foo') + assert_cursor(3) + assert_cursor_max(3) + assert_line('foo') + input_keys("\C-n") + assert_byte_pointer_size('foo_bar') + assert_cursor(7) + assert_cursor_max(7) + assert_line('foo_bar') + input_keys("_\C-n") + assert_byte_pointer_size('foo_bar_') + assert_cursor(8) + assert_cursor_max(8) + assert_line('foo_bar_') + input_keys("\C-n") + assert_byte_pointer_size('foo_bar_baz') + assert_cursor(11) + assert_cursor_max(11) + assert_line('foo_bar_baz') + input_keys("\C-n") + assert_byte_pointer_size('foo_bar_') + assert_cursor(8) + assert_cursor_max(8) + assert_line('foo_bar_') + end + + def test_completion_journey_reverse + @line_editor.completion_proc = proc { |word| + %w{ + foo_bar + foo_bar_baz + } + } + input_keys('foo') + assert_byte_pointer_size('foo') + assert_cursor(3) + assert_cursor_max(3) + assert_line('foo') + input_keys("\C-p") + assert_byte_pointer_size('foo') + assert_cursor(3) + assert_cursor_max(3) + assert_line('foo') + input_keys("\C-p") + assert_byte_pointer_size('foo_bar_baz') + assert_cursor(11) + assert_cursor_max(11) + assert_line('foo_bar_baz') + input_keys("\C-p") + assert_byte_pointer_size('foo_bar') + assert_cursor(7) + assert_cursor_max(7) + assert_line('foo_bar') + input_keys("\C-p") + assert_byte_pointer_size('foo') + assert_cursor(3) + assert_cursor_max(3) + assert_line('foo') + input_keys("\C-p") + assert_byte_pointer_size('foo_bar_baz') + assert_cursor(11) + assert_cursor_max(11) + assert_line('foo_bar_baz') + input_keys("\C-h\C-p") + assert_byte_pointer_size('foo_bar_ba') + assert_cursor(10) + assert_cursor_max(10) + assert_line('foo_bar_ba') + input_keys("\C-p") + assert_byte_pointer_size('foo_bar_baz') + assert_cursor(11) + assert_cursor_max(11) + assert_line('foo_bar_baz') + input_keys("\C-p") + assert_byte_pointer_size('foo_bar_ba') + assert_cursor(10) + assert_cursor_max(10) + assert_line('foo_bar_ba') + end + + def test_completion_journey_in_middle_of_line + @line_editor.completion_proc = proc { |word| + %w{ + foo_bar + foo_bar_baz + } + } + input_keys('abcde fo ABCDE') + assert_line('abcde fo ABCDE') + input_keys("\C-[" + 'h' * 5 + "i\C-n") + assert_byte_pointer_size('abcde fo') + assert_cursor(8) + assert_cursor_max(14) + assert_line('abcde fo ABCDE') + input_keys("\C-n") + assert_byte_pointer_size('abcde foo_bar') + assert_cursor(13) + assert_cursor_max(19) + assert_line('abcde foo_bar ABCDE') + input_keys("\C-n") + assert_byte_pointer_size('abcde foo_bar_baz') + assert_cursor(17) + assert_cursor_max(23) + assert_line('abcde foo_bar_baz ABCDE') + input_keys("\C-n") + assert_byte_pointer_size('abcde fo') + assert_cursor(8) + assert_cursor_max(14) + assert_line('abcde fo ABCDE') + input_keys("\C-n") + assert_byte_pointer_size('abcde foo_bar') + assert_cursor(13) + assert_cursor_max(19) + assert_line('abcde foo_bar ABCDE') + input_keys("_\C-n") + assert_byte_pointer_size('abcde foo_bar_') + assert_cursor(14) + assert_cursor_max(20) + assert_line('abcde foo_bar_ ABCDE') + input_keys("\C-n") + assert_byte_pointer_size('abcde foo_bar_baz') + assert_cursor(17) + assert_cursor_max(23) + assert_line('abcde foo_bar_baz ABCDE') + input_keys("\C-n") + assert_byte_pointer_size('abcde foo_bar_') + assert_cursor(14) + assert_cursor_max(20) + assert_line('abcde foo_bar_ ABCDE') + input_keys("\C-n") + assert_byte_pointer_size('abcde foo_bar_baz') + assert_cursor(17) + assert_cursor_max(23) + assert_line('abcde foo_bar_baz ABCDE') + end + + def test_ed_move_to_beg + input_keys("abcde\C-[^") + assert_byte_pointer_size('') + assert_cursor(0) + assert_cursor_max(5) + input_keys("0\C-ki") + input_keys(" abcde\C-[^") + assert_byte_pointer_size(' ') + assert_cursor(1) + assert_cursor_max(6) + input_keys("0\C-ki") + input_keys(" abcde ABCDE \C-[^") + assert_byte_pointer_size(' ') + assert_cursor(3) + assert_cursor_max(17) + end + + def test_vi_delete_meta + input_keys("aaa bbb ccc ddd eee\C-[02w") + assert_byte_pointer_size('aaa bbb ') + assert_cursor(8) + assert_cursor_max(19) + assert_line('aaa bbb ccc ddd eee') + input_keys('dw') + assert_byte_pointer_size('aaa bbb ') + assert_cursor(8) + assert_cursor_max(15) + assert_line('aaa bbb ddd eee') + input_keys('db') + assert_byte_pointer_size('aaa ') + assert_cursor(4) + assert_cursor_max(11) + assert_line('aaa ddd eee') + end +end diff --git a/test/reline/test_kill_ring.rb b/test/reline/test_kill_ring.rb new file mode 100644 index 00000000000000..8bebfe2177c782 --- /dev/null +++ b/test/reline/test_kill_ring.rb @@ -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 diff --git a/tool/sync_default_gems.rb b/tool/sync_default_gems.rb index 2bef1c621c95fb..b69d1dcba00178 100644 --- a/tool/sync_default_gems.rb +++ b/tool/sync_default_gems.rb @@ -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 @@ 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`