diff options
35 files changed, 8391 insertions, 1129 deletions
diff --git a/doc/irb/irb.rd.ja b/doc/irb/irb.rd.ja index 85b6536ee4..0522b3fa3d 100644 --- a/doc/irb/irb.rd.ja +++ b/doc/irb/irb.rd.ja @@ -70,8 +70,6 @@ irbの使い方は, Rubyさえ知っていればいたって簡単です. 基本 --back-trace-limit n バックトレース表示をバックトレースの頭から n, 後ろ からnだけ行なう. デフォルトは16 - --irb_debug n irbのデバッグデバッグレベルをnに設定する(利用しな - い方が無難でしょう). -v, --version irbのバージョンを表示する = コンフィギュレーション @@ -97,7 +95,6 @@ irb起動時に``~/.irbrc''を読み込みます. もし存在しない場合は IRB.conf[:IGNORE_EOF] = false IRB.conf[:PROMPT_MODE] = :DEFAULT IRB.conf[:PROMPT] = {...} - IRB.conf[:DEBUG_LEVEL]=0 IRB.conf[:VERBOSE]=true == プロンプトの設定 @@ -183,9 +180,6 @@ irb拡張コマンドは, 簡単な名前と頭に`irb_'をつけた名前と両 バックトレース表示をバックトレースの頭からn, 後ろからnだけ行なう. デフォルトは16 ---- conf.debug_level = N - irb用のデバッグレベルの設定 - --- conf.ignore_eof = true/false ^Dが入力された時の動作を設定する. trueの時は^Dを無視する, falseの 時はirbを終了する. diff --git a/lib/irb.rb b/lib/irb.rb index 78d0b7c8cf..897cfae957 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -59,6 +59,8 @@ require "irb/version" # -W[level=2] Same as `ruby -W` # --inspect Use `inspect' for output (default except for bc mode) # --noinspect Don't use inspect for output +# --reidline Use Reidline extension module +# --noreidline Don't use Reidline extension module # --readline Use Readline extension module # --noreadline Don't use Readline extension module # --prompt prompt-mode @@ -66,14 +68,13 @@ require "irb/version" # Switch prompt mode. Pre-defined prompt modes are # `default', `simple', `xmp' and `inf-ruby' # --inf-ruby-mode Use prompt appropriate for inf-ruby-mode on emacs. -# Suppresses --readline. +# Suppresses --reidline and --readline. # --simple-prompt Simple prompt mode # --noprompt No prompt mode # --tracer Display trace for each execution of commands. # --back-trace-limit n # Display backtrace top n and tail n. The default # value is 16. -# --irb_debug n Set internal debug level to n (not for popular use) # -v, --version Print the version of irb # # == Configuration @@ -95,13 +96,13 @@ require "irb/version" # IRB.conf[:IRB_RC] = nil # IRB.conf[:BACK_TRACE_LIMIT]=16 # IRB.conf[:USE_LOADER] = false +# IRB.conf[:USE_REIDLINE] = nil # IRB.conf[:USE_READLINE] = nil # IRB.conf[:USE_TRACER] = false # IRB.conf[:IGNORE_SIGINT] = true # IRB.conf[:IGNORE_EOF] = false # IRB.conf[:PROMPT_MODE] = :DEFAULT # IRB.conf[:PROMPT] = {...} -# IRB.conf[:DEBUG_LEVEL]=0 # # === Auto indentation # @@ -410,9 +411,7 @@ module IRB @context = Context.new(self, workspace, input_method, output_method) @context.main.extend ExtendCommandBundle @signal_status = :IN_IRB - @scanner = RubyLex.new - @scanner.exception_on_syntax_error = false end def run(conf = IRB.conf) diff --git a/lib/irb/completion.rb b/lib/irb/completion.rb index 390e7254dd..4cd9427743 100644 --- a/lib/irb/completion.rb +++ b/lib/irb/completion.rb @@ -8,11 +8,10 @@ # require "readline" +require "rdoc" module IRB module InputCompletor # :nodoc: - - # Set of reserved words used by Ruby, you should not use these for # constants or variables ReservedWords = %w[ @@ -35,6 +34,8 @@ module IRB yield ] + BASIC_WORD_BREAK_CHARACTERS = " \t\n`><=;|&{(" + CompletionProc = proc { |input| bind = IRB.conf[:MAIN_CONTEXT].workspace.binding @@ -195,6 +196,14 @@ module IRB end } + RDocRIDriver = RDoc::RI::Driver.new + PerfectMatchedProc = proc { |matched| + begin + RDocRIDriver.display_name(matched) + rescue RDoc::RI::Driver::NotFoundError + end + } + # Set of available operators in Ruby Operators = %w[% & * ** + - / < << <= <=> == === =~ > >= >> [] []= ^ ! != !~] @@ -236,9 +245,3 @@ module IRB end end end - -if Readline.respond_to?("basic_word_break_characters=") - Readline.basic_word_break_characters= " \t\n`><=;|&{(" -end -Readline.completion_append_character = nil -Readline.completion_proc = IRB::InputCompletor::CompletionProc diff --git a/lib/irb/context.rb b/lib/irb/context.rb index e8e6a118e6..866eb1de9d 100644 --- a/lib/irb/context.rb +++ b/lib/irb/context.rb @@ -22,7 +22,7 @@ module IRB # # The optional +input_method+ argument: # - # +nil+:: uses stdin or Readline + # +nil+:: uses stdin or Reidline or Readline # +String+:: uses a File # +other+:: uses this as InputMethod def initialize(irb, workspace = nil, input_method = nil, output_method = nil) @@ -40,6 +40,7 @@ module IRB @load_modules = IRB.conf[:LOAD_MODULES] @use_readline = IRB.conf[:USE_READLINE] + @use_reidline = IRB.conf[:USE_REIDLINE] @verbose = IRB.conf[:VERBOSE] @io = nil @@ -64,23 +65,41 @@ module IRB case input_method when nil - case use_readline? + @io = nil + case use_reidline? when nil - if (defined?(ReadlineInputMethod) && STDIN.tty? && - IRB.conf[:PROMPT_MODE] != :INF_RUBY) - @io = ReadlineInputMethod.new + if STDIN.tty? && IRB.conf[:PROMPT_MODE] != :INF_RUBY && !use_readline? + @io = ReidlineInputMethod.new else - @io = StdioInputMethod.new + @io = nil end when false - @io = StdioInputMethod.new + @io = nil when true - if defined?(ReadlineInputMethod) - @io = ReadlineInputMethod.new + @io = ReidlineInputMethod.new + end + unless @io + case use_readline? + when nil + if (defined?(ReadlineInputMethod) && STDIN.tty? && + IRB.conf[:PROMPT_MODE] != :INF_RUBY) + @io = ReadlineInputMethod.new + else + @io = nil + end + when false + @io = nil + when true + if defined?(ReadlineInputMethod) + @io = ReadlineInputMethod.new + else + @io = nil + end else - @io = StdioInputMethod.new + @io = nil end end + @io = StdioInputMethod.new unless @io when String @io = FileInputMethod.new(input_method) @@ -101,7 +120,6 @@ module IRB if @echo.nil? @echo = true end - self.debug_level = IRB.conf[:DEBUG_LEVEL] end # The top-level workspace, see WorkSpace#main @@ -117,9 +135,9 @@ module IRB attr_reader :thread # The current input method # - # Can be either StdioInputMethod, ReadlineInputMethod, FileInputMethod or - # other specified when the context is created. See ::new for more - # information on +input_method+. + # Can be either StdioInputMethod, ReadlineInputMethod, + # ReidlineInputMethod, FileInputMethod or other specified when the + # context is created. See ::new for more # information on +input_method+. attr_accessor :io # Current irb session @@ -137,6 +155,12 @@ module IRB # +input_method+ passed to Context.new attr_accessor :irb_path + # Whether +Reidline+ is enabled or not. + # + # A copy of the default <code>IRB.conf[:USE_REIDLINE]</code> + # + # See #use_reidline= for more information. + attr_reader :use_reidline # Whether +Readline+ is enabled or not. # # A copy of the default <code>IRB.conf[:USE_READLINE]</code> @@ -211,10 +235,6 @@ module IRB # # A copy of the default <code>IRB.conf[:VERBOSE]</code> attr_accessor :verbose - # The debug level of irb - # - # See #debug_level= for more information. - attr_reader :debug_level # The limit of backtrace lines displayed as top +n+ and tail +n+. # @@ -225,6 +245,8 @@ module IRB # See IRB@Command+line+options for more command line options. attr_accessor :back_trace_limit + # Alias for #use_reidline + alias use_reidline? use_reidline # Alias for #use_readline alias use_readline? use_readline # Alias for #rc @@ -236,7 +258,9 @@ module IRB # Returns whether messages are displayed or not. def verbose? if @verbose.nil? - if defined?(ReadlineInputMethod) && @io.kind_of?(ReadlineInputMethod) + if @io.kind_of?(ReidlineInputMethod) + false + elsif defined?(ReadlineInputMethod) && @io.kind_of?(ReadlineInputMethod) false elsif !STDIN.tty? or @io.kind_of?(FileInputMethod) true @@ -249,9 +273,11 @@ module IRB end # Whether #verbose? is +true+, and +input_method+ is either - # StdioInputMethod or ReadlineInputMethod, see #io for more information. + # StdioInputMethod or ReidlineInputMethod or ReadlineInputMethod, see #io + # for more information. def prompting? verbose? || (STDIN.tty? && @io.kind_of?(StdioInputMethod) || + @io.kind_of?(ReidlineInputMethod) || (defined?(ReadlineInputMethod) && @io.kind_of?(ReadlineInputMethod))) end @@ -361,21 +387,6 @@ module IRB print "Do nothing." end - # Sets the debug level of irb - # - # Can also be set using the +--irb_debug+ command line option. - # - # See IRB@Command+line+options for more command line options. - def debug_level=(value) - @debug_level = value - RubyLex.debug_level = value - end - - # Whether or not debug mode is enabled, see #debug_level=. - def debug? - @debug_level > 0 - end - def evaluate(line, line_no, exception: nil) # :nodoc: @line_no = line_no if exception diff --git a/lib/irb/init.rb b/lib/irb/init.rb index 2066d8cb64..ec413679df 100644 --- a/lib/irb/init.rb +++ b/lib/irb/init.rb @@ -112,8 +112,6 @@ module IRB # :nodoc: @CONF[:LC_MESSAGES] = Locale.new @CONF[:AT_EXIT] = [] - - @CONF[:DEBUG_LEVEL] = 0 end def IRB.init_error @@ -165,6 +163,10 @@ module IRB # :nodoc: @CONF[:USE_READLINE] = true when "--noreadline" @CONF[:USE_READLINE] = false + when "--reidline" + @CONF[:USE_REIDLINE] = true + when "--noreidline" + @CONF[:USE_REIDLINE] = false when "--echo" @CONF[:ECHO] = true when "--noecho" @@ -191,8 +193,6 @@ module IRB # :nodoc: @CONF[:CONTEXT_MODE] = ($1 || argv.shift).to_i when "--single-irb" @CONF[:SINGLE_IRB] = true - when /^--irb_debug(?:=(.+))?/ - @CONF[:DEBUG_LEVEL] = ($1 || argv.shift).to_i when "-v", "--version" print IRB.version, "\n" exit 0 diff --git a/lib/irb/input-method.rb b/lib/irb/input-method.rb index f491d5a760..e29d282d2c 100644 --- a/lib/irb/input-method.rb +++ b/lib/irb/input-method.rb @@ -11,6 +11,8 @@ # require_relative 'src_encoding' require_relative 'magic-file' +require_relative "completion" +require 'reline' module IRB STDIN_FILE_NAME = "(line)" # :nodoc: @@ -140,6 +142,12 @@ module IRB @stdin = IO.open(STDIN.to_i, :external_encoding => IRB.conf[:LC_MESSAGES].encoding, :internal_encoding => "-") @stdout = IO.open(STDOUT.to_i, 'w', :external_encoding => IRB.conf[:LC_MESSAGES].encoding, :internal_encoding => "-") + + if Readline.respond_to?("basic_word_break_characters=") + Readline.basic_word_break_characters = IRB::InputCompletor::BASIC_WORD_BREAK_CHARACTERS + end + Readline.completion_append_character = nil + Readline.completion_proc = IRB::InputCompletor::CompletionProc end # Reads the next line from this input method. @@ -186,7 +194,84 @@ module IRB def encoding @stdin.external_encoding end + + if Readline.respond_to?("basic_word_break_characters=") + Readline.basic_word_break_characters = IRB::InputCompletor::BASIC_WORD_BREAK_CHARACTERS + end + Readline.completion_append_character = nil + Readline.completion_proc = IRB::InputCompletor::CompletionProc end rescue LoadError end + + class ReidlineInputMethod < InputMethod + include Reline + # Creates a new input method object using Readline + def initialize + super + + @line_no = 0 + @line = [] + @eof = false + + @stdin = IO.open(STDIN.to_i, :external_encoding => IRB.conf[:LC_MESSAGES].encoding, :internal_encoding => "-") + @stdout = IO.open(STDOUT.to_i, 'w', :external_encoding => IRB.conf[:LC_MESSAGES].encoding, :internal_encoding => "-") + + if Reline.respond_to?("basic_word_break_characters=") + Reline.basic_word_break_characters = IRB::InputCompletor::BASIC_WORD_BREAK_CHARACTERS + end + Reline.completion_append_character = nil + Reline.completion_proc = IRB::InputCompletor::CompletionProc + Reline.dig_perfect_match_proc = IRB::InputCompletor::PerfectMatchedProc + end + + def check_termination(&block) + @check_termination_proc = block + end + + # Reads the next line from this input method. + # + # See IO#gets for more information. + def gets + Reline.input = @stdin + Reline.output = @stdout + if l = readmultiline(@prompt, false, &@check_termination_proc) + HISTORY.push(l) if !l.empty? + @line[@line_no += 1] = l + "\n" + else + @eof = true + l + end + end + + # Whether the end of this input method has been reached, returns +true+ + # if there is no more data to read. + # + # See IO#eof? for more information. + def eof? + @eof + end + + # Whether this input method is still readable when there is no more data to + # read. + # + # See IO#eof for more information. + def readable_after_eof? + true + end + + # Returns the current line number for #io. + # + # #line counts the number of times #gets is called. + # + # See IO#lineno for more information. + def line(line_no) + @line[line_no] + end + + # The external encoding for standard input. + def encoding + @stdin.external_encoding + end + end end diff --git a/lib/irb/lc/help-message b/lib/irb/lc/help-message index d43c6a1695..d1a66dddda 100644 --- a/lib/irb/lc/help-message +++ b/lib/irb/lc/help-message @@ -39,7 +39,6 @@ Usage: irb.rb [options] [programfile] [arguments] --back-trace-limit n Display backtrace top n and tail n. The default value is 16. - --irb_debug n Set internal debug level to n (not for popular use) --verbose Show details --noverbose Don't show details -v, --version Print the version of irb diff --git a/lib/irb/lc/ja/help-message b/lib/irb/lc/ja/help-message index 1b24d14d28..7a15f973c6 100644 --- a/lib/irb/lc/ja/help-message +++ b/lib/irb/lc/ja/help-message @@ -41,8 +41,6 @@ Usage: irb.rb [options] [programfile] [arguments] バックトレース表示をバックトレースの頭から n, 後ろ からnだけ行なう. デフォルトは16 - --irb_debug n irbのデバッグレベルをnに設定する(非推奨). - --verbose 詳細なメッセージを出力する. --noverbose 詳細なメッセージを出力しない(デフォルト). -v, --version irbのバージョンを表示する. diff --git a/lib/irb/ruby-lex.rb b/lib/irb/ruby-lex.rb index 555d1f024f..c4bec4a854 100644 --- a/lib/irb/ruby-lex.rb +++ b/lib/irb/ruby-lex.rb @@ -11,73 +11,39 @@ # require "e2mmap" -require_relative "slex" -require_relative "ruby-token" +require "ripper" # :stopdoc: class RubyLex extend Exception2MessageMapper - def_exception(:AlreadyDefinedToken, "Already defined token(%s)") - def_exception(:TkReading2TokenNoKey, "key nothing(key='%s')") - def_exception(:TkSymbol2TokenNoKey, "key nothing(key='%s')") - def_exception(:TkReading2TokenDuplicateError, - "key duplicate(token_n='%s', key='%s')") - def_exception(:SyntaxError, "%s") - def_exception(:TerminateLineInput, "Terminate Line Input") - include RubyToken - - class << self - attr_accessor :debug_level - def debug? - @debug_level > 0 - end - end - @debug_level = 0 - def initialize - lex_init - set_input(STDIN) - - @seek = 0 @exp_line_no = @line_no = 1 - @base_char_no = 0 - @char_no = 0 - @rests = [] - @readed = [] - @here_readed = [] - @indent = 0 - @indent_stack = [] - @lex_state = EXPR_BEG - @space_seen = false - @here_header = false - @post_symbeg = false - @continue = false @line = "" - - @skip_space = false - @readed_auto_clean_up = false - @exception_on_syntax_error = true - @prompt = nil end - attr_accessor :skip_space - attr_accessor :readed_auto_clean_up - attr_accessor :exception_on_syntax_error - - attr_reader :seek - attr_reader :char_no - attr_reader :line_no - attr_reader :indent - # io functions def set_input(io, p = nil, &block) @io = io + if @io.respond_to?(:check_termination) + @io.check_termination do |code| + @tokens = Ripper.lex(code) + continue = process_continue + code_block_open = check_code_block(code) + indent = process_nesting_level + ltype = process_literal_type + if code_block_open or ltype or continue or indent > 0 + false + else + true + end + end + end if p.respond_to?(:call) @input = p elsif block_given? @@ -87,112 +53,6 @@ class RubyLex end end - def get_readed - if idx = @readed.rindex("\n") - @base_char_no = @readed.size - (idx + 1) - else - @base_char_no += @readed.size - end - - readed = @readed.join("") - @readed = [] - readed - end - - def getc - while @rests.empty? - @rests.push nil unless buf_input - end - c = @rests.shift - if @here_header - @here_readed.push c - else - @readed.push c - end - @seek += 1 - if c == "\n" - @line_no += 1 - @char_no = 0 - else - @char_no += 1 - end - c - end - - def gets - l = "" - while c = getc - l.concat(c) - break if c == "\n" - end - return nil if l == "" and c.nil? - l - end - - def eof? - @io.eof? - end - - def getc_of_rests - if @rests.empty? - nil - else - getc - end - end - - def ungetc(c = nil) - if @here_readed.empty? - c2 = @readed.pop - else - c2 = @here_readed.pop - end - c = c2 unless c - @rests.unshift c #c = - @seek -= 1 - if c == "\n" - @line_no -= 1 - if idx = @readed.rindex("\n") - @char_no = idx + 1 - else - @char_no = @base_char_no + @readed.size - end - else - @char_no -= 1 - end - end - - def peek_equal?(str) - chrs = str.split(//) - until @rests.size >= chrs.size - return false unless buf_input - end - @rests[0, chrs.size] == chrs - end - - def peek_match?(regexp) - while @rests.empty? - return false unless buf_input - end - regexp =~ @rests.join("") - end - - def peek(i = 0) - while @rests.size <= i - return nil unless buf_input - end - @rests[i] - end - - def buf_input - prompt - line = @input.call - return nil unless line - @rests.concat line.chars.to_a - true - end - private :buf_input - def set_prompt(p = nil, &block) p = block if block_given? if p.respond_to?(:call) @@ -210,20 +70,11 @@ class RubyLex def initialize_input @ltype = nil - @quoted = nil @indent = 0 - @indent_stack = [] - @lex_state = EXPR_BEG - @space_seen = false - @here_header = false - @continue = false - @post_symbeg = false - - prompt - @line = "" @exp_line_no = @line_no + @code_block_open = false end def each_top_level_statement @@ -231,13 +82,14 @@ class RubyLex catch(:TERM_INPUT) do loop do begin - @continue = false prompt unless l = lex throw :TERM_INPUT if @line == '' else + @line_no += 1 + next if l == "\n" @line.concat l - if @ltype or @continue or @indent > 0 + if @code_block_open or @ltype or @continue or @indent > 0 next end end @@ -250,930 +102,203 @@ class RubyLex @exp_line_no = @line_no @indent = 0 - @indent_stack = [] - prompt rescue TerminateLineInput initialize_input prompt - get_readed end end end end def lex - continue = @continue - while tk = token - case tk - when TkNL, TkEND_OF_SCRIPT - @continue = continue unless continue.nil? - break unless @continue - when TkSPACE, TkCOMMENT - when TkSEMICOLON, TkBEGIN, TkELSE - @continue = continue = false - else - continue = nil - end - end - line = get_readed - if line == "" and tk.kind_of?(TkEND_OF_SCRIPT) || tk.nil? - nil - else - line - end - end - - def token - @prev_seek = @seek - @prev_line_no = @line_no - @prev_char_no = @char_no - begin - begin - tk = @OP.match(self) - @space_seen = tk.kind_of?(TkSPACE) - @lex_state = EXPR_END if @post_symbeg && tk.kind_of?(TkOp) - @post_symbeg = tk.kind_of?(TkSYMBEG) - rescue SyntaxError - raise if @exception_on_syntax_error - tk = TkError.new(@seek, @line_no, @char_no) - end - end while @skip_space and tk.kind_of?(TkSPACE) - if @readed_auto_clean_up - get_readed - end - tk - end - - ENINDENT_CLAUSE = [ - "case", "class", "def", "do", "for", "if", - "module", "unless", "until", "while", "begin" - ] - DEINDENT_CLAUSE = ["end" - ] - - PERCENT_LTYPE = { - "q" => "\'", - "Q" => "\"", - "x" => "\`", - "r" => "/", - "w" => "]", - "W" => "]", - "i" => "]", - "I" => "]", - "s" => ":" - } - - PERCENT_PAREN = { - "{" => "}", - "[" => "]", - "<" => ">", - "(" => ")" - } - - Ltype2Token = { - "\'" => TkSTRING, - "\"" => TkSTRING, - "\`" => TkXSTRING, - "/" => TkREGEXP, - "]" => TkDSTRING, - ":" => TkSYMBOL - } - DLtype2Token = { - "\"" => TkDSTRING, - "\`" => TkDXSTRING, - "/" => TkDREGEXP, - } - - def lex_init() - @OP = IRB::SLex.new - @OP.def_rules("\0", "\004", "\032") do |op, io| - Token(TkEND_OF_SCRIPT) - end - - @OP.def_rules(" ", "\t", "\f", "\r", "\13") do |op, io| - @space_seen = true - while getc =~ /[ \t\f\r\13]/; end - ungetc - Token(TkSPACE) - end - - @OP.def_rule("#") do |op, io| - identify_comment - end - - @OP.def_rule("=begin", - proc{|op, io| @prev_char_no == 0 && peek(0) =~ /\s/}) do - |op, io| - @ltype = "=" - until getc == "\n"; end - until peek_equal?("=end") && peek(4) =~ /\s/ - until getc == "\n"; end - end - gets - @ltype = nil - Token(TkRD_COMMENT) - end - - @OP.def_rule("\n") do |op, io| - print "\\n\n" if RubyLex.debug? - case @lex_state - when EXPR_BEG, EXPR_FNAME, EXPR_DOT - @continue = true - else - @continue = false - @lex_state = EXPR_BEG - until (@indent_stack.empty? || - [TkLPAREN, TkLBRACK, TkLBRACE, - TkfLPAREN, TkfLBRACK, TkfLBRACE].include?(@indent_stack.last)) - @indent_stack.pop - end - end - @here_header = false - @here_readed = [] - Token(TkNL) - end - - @OP.def_rules("*", "**", - "=", "==", "===", - "=~", "<=>", - "<", "<=", - ">", ">=", ">>", - "!", "!=", "!~") do - |op, io| - case @lex_state - when EXPR_FNAME, EXPR_DOT - @lex_state = EXPR_ARG - else - @lex_state = EXPR_BEG - end - Token(op) - end - - @OP.def_rules("<<") do - |op, io| - tk = nil - if @lex_state != EXPR_END && @lex_state != EXPR_CLASS && - (@lex_state != EXPR_ARG || @space_seen) - c = peek(0) - if /[-~"'`\w]/ =~ c - tk = identify_here_document - end - end - unless tk - tk = Token(op) - case @lex_state - when EXPR_FNAME, EXPR_DOT - @lex_state = EXPR_ARG - else - @lex_state = EXPR_BEG - end - end - tk - end - - @OP.def_rules("'", '"') do - |op, io| - identify_string(op) - end - - @OP.def_rules("`") do - |op, io| - if @lex_state == EXPR_FNAME - @lex_state = EXPR_END - Token(op) - else - identify_string(op) - end - end - - @OP.def_rules('?') do - |op, io| - if @lex_state == EXPR_END - @lex_state = EXPR_BEG - Token(TkQUESTION) - else - ch = getc - if @lex_state == EXPR_ARG && ch =~ /\s/ - ungetc - @lex_state = EXPR_BEG; - Token(TkQUESTION) - else - if (ch == '\\') - read_escape - end - @lex_state = EXPR_END - Token(TkINTEGER) - end - end - end - - @OP.def_rules("&", "&&", "|", "||") do - |op, io| - @lex_state = EXPR_BEG - Token(op) - end - - @OP.def_rules("+=", "-=", "*=", "**=", - "&=", "|=", "^=", "<<=", ">>=", "||=", "&&=") do - |op, io| - @lex_state = EXPR_BEG - op =~ /^(.*)=$/ - Token(TkOPASGN, $1) - end - - @OP.def_rule("+@", proc{|op, io| @lex_state == EXPR_FNAME}) do - |op, io| - @lex_state = EXPR_ARG - Token(op) - end - - @OP.def_rule("-@", proc{|op, io| @lex_state == EXPR_FNAME}) do - |op, io| - @lex_state = EXPR_ARG - Token(op) - end - - @OP.def_rules("+", "-") do - |op, io| - catch(:RET) do - if @lex_state == EXPR_ARG - if @space_seen and peek(0) =~ /[0-9]/ - throw :RET, identify_number - else - @lex_state = EXPR_BEG - end - elsif @lex_state != EXPR_END and peek(0) =~ /[0-9]/ - throw :RET, identify_number - else - @lex_state = EXPR_BEG - end - Token(op) - end - end - - @OP.def_rule(".") do - |op, io| - @lex_state = EXPR_BEG - if peek(0) =~ /[0-9]/ - ungetc - identify_number - else - # for "obj.if" etc. - @lex_state = EXPR_DOT - Token(TkDOT) - end - end - - @OP.def_rules("..", "...") do - |op, io| - @lex_state = EXPR_BEG - Token(op) - end - - lex_int2 - end - - def lex_int2 - @OP.def_rules("]", "}", ")") do - |op, io| - @lex_state = EXPR_END - @indent -= 1 - @indent_stack.pop - Token(op) - end - - @OP.def_rule(":") do - |op, io| - if @lex_state == EXPR_END || peek(0) =~ /\s/ - @lex_state = EXPR_BEG - Token(TkCOLON) - else - @lex_state = EXPR_FNAME - Token(TkSYMBEG) - end - end - - @OP.def_rule("::") do - |op, io| - if @lex_state == EXPR_BEG or @lex_state == EXPR_ARG && @space_seen - @lex_state = EXPR_BEG - Token(TkCOLON3) - else - @lex_state = EXPR_DOT - Token(TkCOLON2) - end - end - - @OP.def_rule("/") do - |op, io| - if @lex_state == EXPR_BEG || @lex_state == EXPR_MID - identify_string(op) - elsif peek(0) == '=' - getc - @lex_state = EXPR_BEG - Token(TkOPASGN, "/") #/) - elsif @lex_state == EXPR_ARG and @space_seen and peek(0) !~ /\s/ - identify_string(op) - else - @lex_state = EXPR_BEG - Token("/") #/) - end - end - - @OP.def_rules("^") do - |op, io| - @lex_state = EXPR_BEG - Token("^") - end - - @OP.def_rules(",") do - |op, io| - @lex_state = EXPR_BEG - Token(op) - end - - @OP.def_rules(";") do - |op, io| - @lex_state = EXPR_BEG - until (@indent_stack.empty? || - [TkLPAREN, TkLBRACK, TkLBRACE, - TkfLPAREN, TkfLBRACK, TkfLBRACE].include?(@indent_stack.last)) - @indent_stack.pop - end - Token(op) - end - - @OP.def_rule("~") do - |op, io| - @lex_state = EXPR_BEG - Token("~") - end - - @OP.def_rule("~@", proc{|op, io| @lex_state == EXPR_FNAME}) do - |op, io| - @lex_state = EXPR_BEG - Token("~") - end - - @OP.def_rule("(") do - |op, io| - @indent += 1 - if @lex_state == EXPR_BEG || @lex_state == EXPR_MID - @lex_state = EXPR_BEG - tk_c = TkfLPAREN - else - @lex_state = EXPR_BEG - tk_c = TkLPAREN - end - @indent_stack.push tk_c - Token(tk_c) - end - - @OP.def_rule("[]", proc{|op, io| @lex_state == EXPR_FNAME}) do - |op, io| - @lex_state = EXPR_ARG - Token("[]") - end - - @OP.def_rule("[]=", proc{|op, io| @lex_state == EXPR_FNAME}) do - |op, io| - @lex_state = EXPR_ARG - Token("[]=") - end - - @OP.def_rule("[") do - |op, io| - @indent += 1 - if @lex_state == EXPR_FNAME - tk_c = TkfLBRACK - else - if @lex_state == EXPR_BEG || @lex_state == EXPR_MID - tk_c = TkLBRACK - elsif @lex_state == EXPR_ARG && @space_seen - tk_c = TkLBRACK - else - tk_c = TkfLBRACK - end - @lex_state = EXPR_BEG - end - @indent_stack.push tk_c - Token(tk_c) - end - - @OP.def_rule("{") do - |op, io| - @indent += 1 - if @lex_state != EXPR_END && @lex_state != EXPR_ARG - tk_c = TkLBRACE - else - tk_c = TkfLBRACE - end - @lex_state = EXPR_BEG - @indent_stack.push tk_c - Token(tk_c) - end - - @OP.def_rule('\\') do - |op, io| - if getc == "\n" - @space_seen = true - @continue = true - Token(TkSPACE) - else - read_escape - Token("\\") - end - end - - @OP.def_rule('%') do - |op, io| - if @lex_state == EXPR_BEG || @lex_state == EXPR_MID - identify_quotation - elsif peek(0) == '=' - getc - Token(TkOPASGN, :%) - elsif @lex_state == EXPR_ARG and @space_seen and peek(0) !~ /\s/ - identify_quotation - else - @lex_state = EXPR_BEG - Token("%") #)) - end - end - - @OP.def_rule('$') do - |op, io| - identify_gvar - end - - @OP.def_rule('@') do - |op, io| - if peek(0) =~ /[\w@]/ - ungetc - identify_identifier - else - Token("@") - end - end - - @OP.def_rule("") do - |op, io| - printf "MATCH: start %s: %s\n", op, io.inspect if RubyLex.debug? - if peek(0) =~ /[0-9]/ - t = identify_number - elsif peek(0) =~ /[^\x00-\/:-@\[-^`{-\x7F]/ - t = identify_identifier - end - printf "MATCH: end %s: %s\n", op, io.inspect if RubyLex.debug? - t - end - - p @OP if RubyLex.debug? - end - - def identify_gvar - @lex_state = EXPR_END - - case ch = getc - when /[~_*$?!@\/\\;,=:<>".]/ #" - Token(TkGVAR, "$" + ch) - when "-" - Token(TkGVAR, "$-" + getc) - when "&", "`", "'", "+" - Token(TkBACK_REF, "$"+ch) - when /[1-9]/ - while getc =~ /[0-9]/; end - ungetc - Token(TkNTH_REF) - when /\w/ - ungetc - ungetc - identify_identifier - else - ungetc - Token("$") - end - end - - def identify_identifier - token = "" - if peek(0) =~ /[$@]/ - token.concat(c = getc) - if c == "@" and peek(0) == "@" - token.concat getc - end - end - - while (ch = getc) =~ /[^\x00-\/:-@\[-^`{-\x7F]/ - print ":", ch, ":" if RubyLex.debug? - token.concat ch - end - ungetc - - if (ch == "!" || ch == "?") && token[0,1] =~ /\w/ && peek(0) != "=" - token.concat getc - end - - # almost fix token - - case token - when /^\$/ - return Token(TkGVAR, token) - when /^\@\@/ - @lex_state = EXPR_END - # p Token(TkCVAR, token) - return Token(TkCVAR, token) - when /^\@/ - @lex_state = EXPR_END - return Token(TkIVAR, token) - end - - if @lex_state != EXPR_DOT - print token, "\n" if RubyLex.debug? - - token_c, *trans = TkReading2Token[token] - if token_c - # reserved word? - - if (@lex_state != EXPR_BEG && - @lex_state != EXPR_FNAME && - trans[1]) - # modifiers - token_c = TkSymbol2Token[trans[1]] - @lex_state = trans[0] - else - if @lex_state != EXPR_FNAME and peek(0) != ':' - if ENINDENT_CLAUSE.include?(token) - # check for ``class = val'' etc. - valid = true - case token - when "class" - valid = false unless peek_match?(/^\s*(<<|\w|::)/) - when "def" - valid = false if peek_match?(/^\s*(([+\-\/*&\|^]|<<|>>|\|\||\&\&)=|\&\&|\|\|)/) - when "do" - valid = false if peek_match?(/^\s*([+\-\/*]?=|\*|<|>|\&)/) - when *ENINDENT_CLAUSE - valid = false if peek_match?(/^\s*([+\-\/*]?=|\*|<|>|\&|\|)/) - else - # no nothing - end - if valid - if token == "do" - if ![TkFOR, TkWHILE, TkUNTIL].include?(@indent_stack.last) - @indent += 1 - @indent_stack.push token_c - end - else - @indent += 1 - @indent_stack.push token_c - end - end - - elsif DEINDENT_CLAUSE.include?(token) - @indent -= 1 - @indent_stack.pop - end - @lex_state = trans[0] - else - @lex_state = EXPR_END - end - end - return Token(token_c, token) - end - end - - if @lex_state == EXPR_FNAME - @lex_state = EXPR_END - if peek(0) == '=' - token.concat getc - end - elsif @lex_state == EXPR_BEG || @lex_state == EXPR_DOT - @lex_state = EXPR_ARG - else - @lex_state = EXPR_END - end - - if token[0, 1] =~ /[A-Z]/ - return Token(TkCONSTANT, token) - elsif token[token.size - 1, 1] =~ /[!?]/ - return Token(TkFID, token) - else - return Token(TkIDENTIFIER, token) - end - end - - def identify_here_document - ch = getc - if ch == "-" || ch == "~" - ch = getc - indent = true - end - if /['"`]/ =~ ch - lt = ch - quoted = "" - while (c = getc) && c != lt - quoted.concat c - end - else - lt = '"' - quoted = ch.dup - while (c = getc) && c =~ /\w/ - quoted.concat c - end - ungetc - end - - ltback, @ltype = @ltype, lt - reserve = [] - while ch = getc - reserve.push ch - if ch == "\\" - reserve.push ch = getc - elsif ch == "\n" - break - end - end - - @here_header = false - - line = "" - while ch = getc - if ch == "\n" - if line == quoted - break - end - line = "" - else - line.concat ch unless indent && line == "" && /\s/ =~ ch - if @ltype != "'" && ch == "#" && peek(0) == "{" - identify_string_dvar - end - end - end - - @here_header = true - @here_readed.concat reserve - while ch = reserve.pop - ungetc ch - end - - @ltype = ltback - @lex_state = EXPR_END - Token(Ltype2Token[lt]) + line = @input.call + if @io.respond_to?(:check_termination) + return line # multiline + end + code = @line + (line.nil? ? '' : line) + code.gsub!(/\n*$/, '').concat("\n") + @tokens = Ripper.lex(code) + @continue = process_continue + @code_block_open = check_code_block(code) + @indent = process_nesting_level + @ltype = process_literal_type + line end - def identify_quotation - ch = getc - if lt = PERCENT_LTYPE[ch] - ch = getc - elsif ch =~ /\W/ - lt = "\"" - else - RubyLex.fail SyntaxError, "unknown type of %string" - end - @quoted = ch unless @quoted = PERCENT_PAREN[ch] - identify_string(lt, @quoted) + def process_continue + continued_bits = Ripper::EXPR_BEG | Ripper::EXPR_FNAME | Ripper::EXPR_DOT + # last token is always newline + if @tokens.size >= 2 and @tokens[-2][1] == :on_regexp_end + # end of regexp literal + return false + elsif @tokens.size >= 2 and @tokens[-2][1] == :on_semicolon + return false + elsif @tokens.size >= 2 and @tokens[-2][1] == :on_kw and (@tokens[-2][2] == 'begin' or @tokens[-2][2] == 'else') + return false + elsif [email protected]? and @tokens.last[2] == "\\\n" + return true + elsif @tokens.size >= 2 and @tokens[-2][3].anybits?(continued_bits) + # end of literal except for regexp + return true + end + false end - def identify_number - @lex_state = EXPR_END - - if peek(0) == "0" && peek(1) !~ /[.eE]/ - getc - case peek(0) - when /[xX]/ - ch = getc - match = /[0-9a-fA-F_]/ - when /[bB]/ - ch = getc - match = /[01_]/ - when /[oO]/ - ch = getc - match = /[0-7_]/ - when /[dD]/ - ch = getc - match = /[0-9_]/ - when /[0-7]/ - match = /[0-7_]/ - when /[89]/ - RubyLex.fail SyntaxError, "Invalid octal digit" - else - return Token(TkINTEGER) - end - - len0 = true - non_digit = false - while ch = getc - if match =~ ch - if ch == "_" - if non_digit - RubyLex.fail SyntaxError, "trailing `#{ch}' in number" - else - non_digit = ch - end - else - non_digit = false - len0 = false - end - else - ungetc - if len0 - RubyLex.fail SyntaxError, "numeric literal without digits" - end - if non_digit - RubyLex.fail SyntaxError, "trailing `#{non_digit}' in number" - end - break - end - end - return Token(TkINTEGER) - end - - type = TkINTEGER - allow_point = true - allow_e = true - non_digit = false - while ch = getc - case ch - when /[0-9]/ - non_digit = false - when "_" - non_digit = ch - when allow_point && "." - if non_digit - RubyLex.fail SyntaxError, "trailing `#{non_digit}' in number" - end - type = TkFLOAT - if peek(0) !~ /[0-9]/ - type = TkINTEGER - ungetc - break - end - allow_point = false - when allow_e && "e", allow_e && "E" - if non_digit - RubyLex.fail SyntaxError, "trailing `#{non_digit}' in number" - end - type = TkFLOAT - if peek(0) =~ /[+-]/ - getc - end - allow_e = false - allow_point = false - non_digit = ch - else - if non_digit - RubyLex.fail SyntaxError, "trailing `#{non_digit}' in number" - end - ungetc - break - end - end - Token(type) + def check_code_block(code) + return true if @tokens.empty? + if @tokens.last[1] == :on_heredoc_beg + return true + end + + begin # check if parser error are available + RubyVM::InstructionSequence.compile(code) + rescue SyntaxError => e + case e.message + when /unterminated (?:string|regexp) meets end of file/ + # "unterminated regexp meets end of file" + # + # example: + # / + # + # "unterminated string meets end of file" + # + # example: + # ' + return true + when /syntax error, unexpected end-of-input/ + # "syntax error, unexpected end-of-input, expecting keyword_end" + # + # example: + # if ture + # hoge + # if false + # fuga + # end + return true + when /syntax error, unexpected keyword_end/ + # "syntax error, unexpected keyword_end" + # + # example: + # if ( + # end + # + # example: + # end + return false + when /unexpected tREGEXP_BEG/ + # "syntax error, unexpected tREGEXP_BEG, expecting keyword_do or '{' or '('" + # + # example: + # method / f / + return false + end + end + + last_lex_state = @tokens.last[3] + if last_lex_state.allbits?(Ripper::EXPR_BEG) + return false + elsif last_lex_state.allbits?(Ripper::EXPR_DOT) + return true + elsif last_lex_state.allbits?(Ripper::EXPR_CLASS) + return true + elsif last_lex_state.allbits?(Ripper::EXPR_FNAME) + return true + elsif last_lex_state.allbits?(Ripper::EXPR_VALUE) + return true + elsif last_lex_state.allbits?(Ripper::EXPR_ARG) + return false + end + + false end - def identify_string(ltype, quoted = ltype) - @ltype = ltype - @quoted = quoted - subtype = nil - begin - nest = 0 - while ch = getc - if @quoted == ch and nest == 0 - break - elsif @ltype != "'" && ch == "#" && peek(0) == "{" - identify_string_dvar - elsif @ltype != "'" && @ltype != "]" && @ltype != ":" and ch == "#" - subtype = true - elsif ch == '\\' and @ltype == "'" #' - case ch = getc - when "\\", "\n", "'" - else - ungetc - end - elsif ch == '\\' #' - read_escape - end - if PERCENT_PAREN.values.include?(@quoted) - if PERCENT_PAREN[ch] == @quoted - nest += 1 - elsif ch == @quoted - nest -= 1 - end + def process_nesting_level + @tokens.inject(0) { |indent, t| + case t[1] + when :on_lbracket, :on_lbrace, :on_lparen + indent += 1 + when :on_rbracket, :on_rbrace, :on_rparen + indent -= 1 + when :on_kw + case t[2] + when 'def', 'do', 'case', 'for', 'begin', 'class', 'module' + indent += 1 + when 'if', 'unless', 'while', 'until', 'rescue' + # postfix if/unless/while/until/rescue must be Ripper::EXPR_LABEL + indent += 1 unless t[3].allbits?(Ripper::EXPR_LABEL) + when 'end' + indent -= 1 end end - if @ltype == "/" - while /[imxoesun]/ =~ peek(0) - getc - end - end - if subtype - Token(DLtype2Token[ltype]) - else - Token(Ltype2Token[ltype]) - end - ensure - @ltype = nil - @quoted = nil - @lex_state = EXPR_END - end + # percent literals are not indented + indent + } end - def identify_string_dvar - begin - getc - - reserve_continue = @continue - reserve_ltype = @ltype - reserve_indent = @indent - reserve_indent_stack = @indent_stack - reserve_state = @lex_state - reserve_quoted = @quoted - - @ltype = nil - @quoted = nil - @indent = 0 - @indent_stack = [] - @lex_state = EXPR_BEG - - loop do - @continue = false - prompt - tk = token - if @ltype or @continue or @indent >= 0 - next + def check_string_literal + i = 0 + start_token = [] + end_type = [] + while i < @tokens.size + t = @tokens[i] + case t[1] + when :on_tstring_beg + start_token << t + end_type << :on_tstring_end + when :on_regexp_beg + start_token << t + end_type << :on_regexp_end + when :on_symbeg + if (i + 1) < @tokens.size and @tokens[i + 1][1] != :on_ident + start_token << t + end_type << :on_tstring_end end - break if tk.kind_of?(TkRBRACE) - end - ensure - @continue = reserve_continue - @ltype = reserve_ltype - @indent = reserve_indent - @indent_stack = reserve_indent_stack - @lex_state = reserve_state - @quoted = reserve_quoted - end + when :on_backtick + start_token << t + end_type << :on_tstring_end + when :on_qwords_beg, :on_words_beg, :on_qsymbols_beg, :on_symbols_beg + start_token << t + end_type << :on_tstring_end + when :on_heredoc_beg + start_token << t + end_type << :on_heredoc_end + when end_type.last + start_token.pop + end_type.pop + end + i += 1 + end + start_token.last.nil? ? '' : start_token.last end - def identify_comment - @ltype = "#" - - while ch = getc - if ch == "\n" - @ltype = nil - ungetc - break - end - end - return Token(TkCOMMENT) - end - - def read_escape - case ch = getc - when "\n", "\r", "\f" - when "\\", "n", "t", "r", "f", "v", "a", "e", "b", "s" #" - when /[0-7]/ - ungetc ch - 3.times do - case ch = getc - when /[0-7]/ - when nil - break - else - ungetc - break - end - end - - when "x" - 2.times do - case ch = getc - when /[0-9a-fA-F]/ - when nil - break - else - ungetc - break - end - end - - when "M" - if (ch = getc) != '-' - ungetc - else - if (ch = getc) == "\\" #" - read_escape - end - end - - when "C", "c" #, "^" - if ch == "C" and (ch = getc) != "-" - ungetc - elsif (ch = getc) == "\\" #" - read_escape + def process_literal_type + start_token = check_string_literal + case start_token[1] + when :on_tstring_beg + case start_token[2] + when ?" then ?" + when /^%.$/ then ?" + when /^%Q.$/ then ?" + when ?' then ?' + when /^%q.$/ then ?' + end + when :on_regexp_beg then ?/ + when :on_symbeg then ?: + when :on_backtick then ?` + when :on_qwords_beg then ?] + when :on_words_beg then ?] + when :on_qsymbols_beg then ?] + when :on_symbols_beg then ?] + when :on_heredoc_beg + start_token[2] =~ /<<[-~]?(['"`])[_a-zA-Z0-9]+\1/ + case $1 + when ?" then ?" + when ?' then ?' + when ?` then ?` + else ?" end else - # other characters + nil end end end diff --git a/lib/rdoc/ri/paths.rb b/lib/rdoc/ri/paths.rb index d41e610591..f76721d318 100644 --- a/lib/rdoc/ri/paths.rb +++ b/lib/rdoc/ri/paths.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'rdoc/ri' +require 'rdoc/rdoc' ## # The directories where ri data lives. Paths can be enumerated via ::each, or diff --git a/lib/readline.rb b/lib/readline.rb new file mode 100644 index 0000000000..690441e05c --- /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 0000000000..b1d7718b7e --- /dev/null +++ b/lib/reline.rb @@ -0,0 +1,196 @@ +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/ + require 'Win32API' + 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 = '"\'' + @completer_quote_characters + @completer_word_break_characters = @basic_word_break_characters.dup + @completion_append_character + 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 + @completion_case_fold + @filename_quote_characters + + @@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 + + @@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 0000000000..f34c4207e5 --- /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(/(?<row>\d+);(?<column>\d+)/) + CursorPos.new(m[:column].to_i - 1, m[:row].to_i - 1) + end + + def self.move_cursor_column(x) + print "\e[#{x + 1}G" + end + + def self.move_cursor_up(x) + if x > 0 + print "\e[#{x}A" if x > 0 + elsif x < 0 + move_cursor_down(-x) + end + end + + def self.move_cursor_down(x) + if x > 0 + print "\e[#{x}B" if x > 0 + elsif x < 0 + move_cursor_up(-x) + end + end + + def self.erase_after_cursor + print "\e[K" + end + + def self.scroll_down(x) + return if x.zero? + print "\e[#{x}S" + end + + def self.clear_screen + print "\e[2J" + print "\e[1;1H" + end + + def prep + int_handle = Signal.trap('INT', 'IGNORE') + otio = `stty -g`.chomp + setting = ' -echo -icrnl cbreak' + if (`stty -a`.scan(/-parenb\b/).first == '-parenb') + setting << ' pass8' + end + setting << ' -ixoff' + `stty #{setting}` + Signal.trap('INT', int_handle) + otio + end + + def deprep(otio) + int_handle = Signal.trap('INT', 'IGNORE') + `stty #{otio}` + Signal.trap('INT', int_handle) + end +end diff --git a/lib/reline/config.rb b/lib/reline/config.rb new file mode 100644 index 0000000000..0800dfd30f --- /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 0000000000..ebe09d2009 --- /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 0000000000..f4abac55d4 --- /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 0000000000..0836cd340c --- /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 0000000000..724f459011 --- /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 0000000000..8585a642ab --- /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 0000000000..ac0a820759 --- /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 0000000000..842fd04697 --- /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 0000000000..7d1030ea0e --- /dev/null +++ b/lib/reline/line_editor.rb @@ -0,0 +1,1357 @@ +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 + @screen_size = Reline.get_screen_size + @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 + 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| + height = 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) + } + 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 # FIXME + 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 0000000000..9ff9ca2128 --- /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 = ['[email protected]'] + + 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/aycabta/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 0000000000..bdb182f59c --- /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 0000000000..4eea3a7cdf --- /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 0000000000..58a69a09a0 --- /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 0000000000..f41ec17489 --- /dev/null +++ b/lib/reline/windows.rb @@ -0,0 +1,132 @@ +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/lib/rubygems.rb b/lib/rubygems.rb index 54bd995b83..16cdb7cb04 100644 --- a/lib/rubygems.rb +++ b/lib/rubygems.rb @@ -18,6 +18,7 @@ require 'rubygems/compatibility' require 'rubygems/defaults' require 'rubygems/deprecate' require 'rubygems/errors' +require 'rubygems/path_support' ## # RubyGems is the Ruby standard for publishing and managing third party diff --git a/test/reline/config_test.rb b/test/reline/config_test.rb new file mode 100644 index 0000000000..34ee407498 --- /dev/null +++ b/test/reline/config_test.rb @@ -0,0 +1,113 @@ +require_relative 'helper' + +class Reline::Config::Test < Reline::TestCase + def setup + @pwd = Dir.pwd + @tmpdir = File.join(Dir.tmpdir, "test_reline_config_#{$$}") + Dir.mkdir(@tmpdir) + 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/helper.rb b/test/reline/helper.rb new file mode 100644 index 0000000000..b73b5d974e --- /dev/null +++ b/test/reline/helper.rb @@ -0,0 +1,71 @@ +$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 + 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/key_actor_emacs_test.rb b/test/reline/key_actor_emacs_test.rb new file mode 100644 index 0000000000..f4dfb952f5 --- /dev/null +++ b/test/reline/key_actor_emacs_test.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/key_actor_vi_test.rb b/test/reline/key_actor_vi_test.rb new file mode 100644 index 0000000000..63618d5fb3 --- /dev/null +++ b/test/reline/key_actor_vi_test.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/key_stroke_test.rb b/test/reline/key_stroke_test.rb new file mode 100644 index 0000000000..b6d5ce4150 --- /dev/null +++ b/test/reline/key_stroke_test.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/kill_ring_test.rb b/test/reline/kill_ring_test.rb new file mode 100644 index 0000000000..8bebfe2177 --- /dev/null +++ b/test/reline/kill_ring_test.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 e7917f7bec..557e70b8c0 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/aycabta/reline # * https://github.com/flori/json # * https://github.com/ruby/psych # * https://github.com/ruby/fileutils @@ -42,6 +43,7 @@ $repositories = { rubygems: 'rubygems/rubygems', bundler: 'bundler/bundler', rdoc: 'ruby/rdoc', + reline: 'aycabta/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` |